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