JAL-3851 merged to develop 2022-03-22
[jalview.git] / src / jalview / rest / RestHandler.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.rest;
22
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.net.BindException;
27 import java.util.HashMap;
28 import java.util.Map;
29
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.eclipse.jetty.server.handler.ContextHandler;
34
35 import jalview.bin.Console;
36 import jalview.httpserver.AbstractRequestHandler;
37 import jalview.httpserver.HttpServer;
38
39 /**
40  * A simple handler to process (or delegate) HTTP requests on /jalview/rest
41  */
42 public class RestHandler extends AbstractRequestHandler
43 {
44
45   public enum Status
46   {
47     STARTED, IN_PROGRESS, FINISHED, ERROR, NOT_RUN
48   }
49
50   public interface EndpointI
51   {
52     public String getPath();
53
54     public String getName();
55
56     public String getParameters();
57
58     public String getDescription();
59
60     public void processEndpoint(HttpServletRequest request,
61             HttpServletResponse response);
62
63   }
64
65   private static final String MY_PATH = "rest";
66
67   private static final String MY_NAME = "Rest";
68
69   private String missingEndpointMessage = null;
70
71   private boolean init = false;
72
73   // map of method names and method handlers
74   private Map<String, EndpointI> endpoints = null;
75
76   protected Map<String, EndpointI> getEndpoints()
77   {
78     return endpoints;
79   }
80
81   /**
82    * Singleton instance of this class
83    */
84   private static RestHandler instance = null;
85
86   /**
87    * Returns the singleton instance of this class
88    * 
89    * @return
90    * @throws BindException
91    */
92   public static RestHandler getInstance() throws BindException
93   {
94     synchronized (RestHandler.class)
95     {
96       if (instance == null)
97       {
98         instance = new RestHandler();
99       }
100     }
101     return instance;
102   }
103
104   /**
105    * Private constructor enforces use of singleton
106    * 
107    * @throws BindException
108    */
109   protected RestHandler() throws BindException
110   {
111     init();
112
113     /*
114      * We don't register the handler here - this is done as a special case in
115      * HttpServer initialisation; to do it here would invite an infinite loop of
116      * RestHandler/HttpServer constructor
117      */
118   }
119
120   /**
121    * Handle a jalview/rest request
122    * 
123    * @throws IOException
124    */
125   @Override
126   protected void processRequest(HttpServletRequest request,
127           HttpServletResponse response) throws IOException
128   {
129     /*
130      * Currently just echoes the request; add helper classes as required to
131      * process requests
132      */
133     System.out.println(request.toString());
134     if (endpoints == null)
135     {
136       final String queryString = request.getQueryString();
137       final String reply = "REST not yet implemented; received "
138               + request.getMethod() + ": " + request.getRequestURL()
139               + (queryString == null ? "" : "?" + queryString);
140       System.out.println(reply);
141
142       response.setHeader("Cache-Control", "no-cache/no-store");
143       response.setHeader("Content-type", "text/plain");
144       final PrintWriter writer = response.getWriter();
145       writer.write(reply);
146       return;
147     }
148
149     String endpointName = getRequestedEndpointName(request);
150
151     if (!endpoints.containsKey(endpointName)
152             || endpoints.get(endpointName) == null)
153     {
154
155       response.setHeader("Cache-Control", "no-cache/no-store");
156       response.setHeader("Content-type", "text/plain");
157       PrintWriter writer = response.getWriter();
158       writer.write(missingEndpointMessage == null
159               ? "REST endpoint '" + endpointName + "' not defined"
160               : missingEndpointMessage);
161       writer.write("\n");
162       writer.write("Available endpoints are:\n");
163       ContextHandler ch = HttpServer.getInstance().getContextHandler(this);
164       String base = HttpServer.getInstance().getUri().toString();
165       String contextPath = ch == null ? "" : ch.getContextPath();
166       for (String key : endpoints.keySet())
167       {
168         writer.write(base + contextPath + "/" + key + "\n");
169       }
170       response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
171       return;
172     }
173
174     response.setHeader("Cache-Control", "no-cache/no-store");
175     response.setHeader("Content-type", "text/plain");
176     EndpointI ep = endpoints.get(endpointName);
177     ep.processEndpoint(request, response);
178
179     return;
180   }
181
182   /**
183    * Returns a display name for this service
184    */
185   @Override
186   public String getName()
187   {
188     return MY_NAME;
189   }
190
191   /**
192    * Initialise methods
193    * 
194    * @throws BindException
195    */
196   protected void init() throws BindException
197   {
198     init(MY_PATH);
199   }
200
201   protected void init(String path) throws BindException
202   {
203     setPath(path);
204     // Override this in extended class
205     // e.g. registerHandler and addEndpoints
206   }
207
208   protected boolean addEndpoint(EndpointI ep)
209   {
210     if (endpoints == null)
211     {
212       endpoints = new HashMap<>();
213     }
214     AbstractEndpoint e = (AbstractEndpoint) ep;
215     endpoints.put(ep.getPath(), ep);
216     return true;
217   }
218
219   protected String getRequestedEndpointName(HttpServletRequest request)
220   {
221     String pathInfo = request.getPathInfo();
222     int slashpos = pathInfo.indexOf('/', 1);
223     return slashpos > 1 ? pathInfo.substring(1, slashpos)
224             : pathInfo.substring(1);
225   }
226
227   protected String[] getEndpointPathParameters(HttpServletRequest request)
228   {
229     String pathInfo = request.getPathInfo();
230     int slashpos = pathInfo.indexOf('/', 1);
231     return slashpos < 1 ? null
232             : pathInfo.substring(slashpos + 1).split("/");
233   }
234
235   protected void returnError(HttpServletRequest request,
236           HttpServletResponse response, String message)
237   {
238     response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
239     String endpointName = getRequestedEndpointName(request);
240     Console.error(getName() + " error: endpoint " + endpointName
241             + " failed: '" + message + "'");
242     try
243     {
244       PrintWriter writer = response.getWriter();
245       writer.write("Endpoint " + endpointName + ": " + message);
246     } catch (IOException e)
247     {
248       Console.debug("Could not write to REST response for endpoint "
249               + endpointName, e);
250     }
251   }
252
253   protected void returnStatus(HttpServletResponse response, String id,
254           Status status)
255   {
256     try
257     {
258       PrintWriter writer = response.getWriter();
259       if (id != null)
260         writer.write("id=" + id + "\n");
261       if (status != null)
262         writer.write("status=" + status.toString() + "\n");
263     } catch (IOException e)
264     {
265       Console.debug("Could not write status to REST response for id:" + id,
266               e);
267     }
268   }
269
270   protected String getRequestBody(HttpServletRequest request)
271           throws IOException
272   {
273     StringBuilder sb = new StringBuilder();
274     BufferedReader reader = request.getReader();
275     try
276     {
277       String line;
278       while ((line = reader.readLine()) != null)
279       {
280         sb.append(line).append('\n');
281       }
282     } finally
283     {
284       reader.close();
285     }
286     return sb.toString();
287   }
288
289 }