JAL-3851 API handling of rest calls
[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 jalview.bin.Cache;
34 import jalview.httpserver.AbstractRequestHandler;
35
36 /**
37  * A simple handler to process (or delegate) HTTP requests on /jalview/rest
38  */
39 public class RestHandler extends AbstractRequestHandler
40 {
41
42   public enum Status
43   {
44     STARTED, IN_PROGRESS, FINISHED, ERROR
45   }
46
47   public interface EndpointI
48   {
49     public void setName(String name);
50
51     public String getName();
52
53     public void processEndpoint(HttpServletRequest request,
54             HttpServletResponse response);
55   }
56
57   public interface Endpoint
58   {
59     public void processEndpoint(String endpointName,
60             HttpServletRequest request, HttpServletResponse response);
61   }
62
63   private static final String MY_PATH = "rest";
64
65   private static final String MY_NAME = "Rest";
66
67   private String missingEndpointMessage = null;
68
69   private boolean init = false;
70
71   // map of method names and method handlers
72   private Map<String, Endpoint> endpoints = null;
73
74   /**
75    * Singleton instance of this class
76    */
77   private static RestHandler instance = null;
78
79   /**
80    * Returns the singleton instance of this class
81    * 
82    * @return
83    * @throws BindException
84    */
85   public static RestHandler getInstance() throws BindException
86   {
87     synchronized (RestHandler.class)
88     {
89       if (instance == null)
90       {
91         instance = new RestHandler();
92       }
93     }
94     return instance;
95   }
96
97   /**
98    * Private constructor enforces use of singleton
99    * 
100    * @throws BindException
101    */
102   protected RestHandler() throws BindException
103   {
104     init();
105
106     /*
107      * We don't register the handler here - this is done as a special case in
108      * HttpServer initialisation; to do it here would invite an infinite loop of
109      * RestHandler/HttpServer constructor
110      */
111   }
112
113   /**
114    * Handle a jalview/rest request
115    * 
116    * @throws IOException
117    */
118   @Override
119   protected void processRequest(HttpServletRequest request,
120           HttpServletResponse response) throws IOException
121   {
122     /*
123      * Currently just echoes the request; add helper classes as required to
124      * process requests
125      */
126     System.out.println(request.toString());
127     if (endpoints == null)
128     {
129       final String queryString = request.getQueryString();
130       final String reply = "REST not yet implemented; received "
131               + request.getMethod() + ": " + request.getRequestURL()
132               + (queryString == null ? "" : "?" + queryString);
133       System.out.println(reply);
134
135       response.setHeader("Cache-Control", "no-cache/no-store");
136       response.setHeader("Content-type", "text/plain");
137       final PrintWriter writer = response.getWriter();
138       writer.write(reply);
139       writer.close();
140       return;
141     }
142
143     String endpointName = getEndpointName(request);
144
145     if (!endpoints.containsKey(endpointName)
146             || endpoints.get(endpointName) == null)
147     {
148
149       response.setHeader("Cache-Control", "no-cache/no-store");
150       response.setHeader("Content-type", "text/plain");
151       response.setStatus(400);
152       PrintWriter writer = response.getWriter();
153       writer.write(missingEndpointMessage == null
154               ? "REST endpoint '" + endpointName + "' not defined"
155               : missingEndpointMessage);
156       writer.close();
157       return;
158     }
159
160     response.setHeader("Cache-Control", "no-cache/no-store");
161     response.setHeader("Content-type", "text/plain");
162     Endpoint ep = endpoints.get(endpointName);
163     ep.processEndpoint(endpointName, request, response);
164
165     return;
166   }
167
168   /**
169    * Returns a display name for this service
170    */
171   @Override
172   public String getName()
173   {
174     return MY_NAME;
175   }
176
177   /**
178    * Initialise methods
179    * 
180    * @throws BindException
181    */
182   protected void init() throws BindException
183   {
184     init(MY_PATH);
185   }
186
187   protected void init(String path) throws BindException
188   {
189     setPath(path);
190     // Override this in extended class
191     // e.g. registerHandler and addEndpoints
192   }
193
194   protected boolean addEndpoint(String name, Endpoint ep)
195   {
196     if (endpoints == null)
197     {
198       endpoints = new HashMap<>();
199     }
200     endpoints.put(name, ep);
201     return true;
202   }
203
204   protected String getEndpointName(HttpServletRequest request)
205   {
206     String pathInfo = request.getPathInfo();
207     int slashpos = pathInfo.indexOf('/', 1);
208     return slashpos > 1 ? pathInfo.substring(1, slashpos)
209             : pathInfo.substring(1);
210   }
211
212   protected String[] getEndpointPathParameters(HttpServletRequest request)
213   {
214     String pathInfo = request.getPathInfo();
215     int slashpos = pathInfo.indexOf('/', 1);
216     return slashpos < 1 ? null
217             : pathInfo.substring(slashpos + 1).split("/");
218   }
219
220   protected void returnError(HttpServletRequest request,
221           HttpServletResponse response, String message)
222   {
223     response.setStatus(500); // set this to something better
224     String endpointName = getEndpointName(request);
225     Cache.error(this.MY_NAME + " error: endpoint " + endpointName
226             + " failed: '" + message + "'");
227     try
228     {
229       PrintWriter writer = response.getWriter();
230       writer.write("Endpoint " + endpointName + ": " + message);
231       writer.close();
232     } catch (IOException e)
233     {
234       Cache.debug(e);
235     }
236   }
237
238   protected void returnStatus(HttpServletResponse response, String id,
239           Status status)
240   {
241     try
242     {
243       PrintWriter writer = response.getWriter();
244       if (id != null)
245         writer.write("id=" + id + "\n");
246       if (status != null)
247         writer.write("status=" + status.toString() + "\n");
248     } catch (IOException e)
249     {
250       Cache.debug(e);
251     }
252   }
253
254   protected String getRequestBody(HttpServletRequest request)
255           throws IOException
256   {
257     StringBuilder sb = new StringBuilder();
258     BufferedReader reader = request.getReader();
259     try
260     {
261       String line;
262       while ((line = reader.readLine()) != null)
263       {
264         sb.append(line).append('\n');
265       }
266     } finally
267     {
268       reader.close();
269     }
270     return sb.toString();
271   }
272
273 }