fe10d1dbff8bafe11dd786fc80f8a159bf6bff5b
[jalview.git] / src / jalview / rest / AbstractEndpointAsync.java
1 package jalview.rest;
2
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import java.util.HashMap;
6 import java.util.Map;
7 import java.util.concurrent.CompletableFuture;
8
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11
12 import jalview.bin.Console;
13 import jalview.rest.RestHandler.Status;
14
15 public abstract class AbstractEndpointAsync extends AbstractEndpoint
16 {
17   public AbstractEndpointAsync(API api, String path, String name,
18           String parameters, String description)
19   {
20     super(api, path, name, parameters, description);
21   }
22
23   protected String idExtension = null;
24
25   protected String id = null;
26
27   protected CompletableFuture<Void> cf = null;
28
29   protected Map<String, CompletableFuture<Void>> cfMap = new HashMap<>();
30
31   protected Map<String, Object> objectsPassedToProcessAsync = new HashMap<>();
32
33   private Status tempStatus = null;
34
35   protected void setCompletableFuture(CompletableFuture<Void> cf)
36   {
37     this.cf = cf;
38     if (getId() != null)
39       cfMap.put(getId(), cf);
40   }
41
42   protected CompletableFuture<Void> getCompletableFuture()
43   {
44     if (cf == null && getId() != null && cfMap.get(getId()) != null)
45       cf = cfMap.get(getId());
46     return this.cf;
47   }
48
49   protected void setId(String id)
50   {
51     this.id = id;
52   }
53
54   protected void setIdExtension(String idExtension)
55   {
56     setId(getPath() + "::" + idExtension);
57   }
58
59   protected String getId()
60   {
61     return this.id;
62   }
63
64   /*
65    * Override the three methods
66    * initialise (get parameters, set id (extension), set cf if using an existing one)
67    * process (what to do in the cf if not using an existing one)
68    * finalise (extra stuff to do at the end of the first call to this)
69    */
70   protected void initialise(HttpServletRequest request,
71           HttpServletResponse response)
72   {
73     // should be overridden
74     // MUST setId(request, extension)
75     this.setId(request, null);
76     // and do this
77     this.saveParameters(request);
78   }
79
80   protected void saveParameters(HttpServletRequest request)
81   {
82     this.getId(request);
83     this.getFromId(request);
84     this.getEndpointPathParameters(request);
85     this.getQueryParameters(request);
86     this.getOptions(request);
87     this.getRequestUrl(request);
88   }
89
90   protected abstract void processAsync(HttpServletRequest request,
91           HttpServletResponse response, final Map<String, Object> finalMap);
92
93   protected void finalise(HttpServletRequest request,
94           HttpServletResponse response)
95   {
96     // can be Overridden
97   }
98
99   @Override
100   public void processEndpoint(HttpServletRequest request,
101           HttpServletResponse response)
102   {
103     tempStatus = null;
104     // subclass method
105     initialise(request, response);
106
107     if (checkStatus(request, response, Status.STARTED))
108     {
109       String alreadyFinishedString = null;
110       if (getStatus() == Status.FINISHED)
111       {
112         alreadyFinishedString = finishedResponseString(request, response);
113       }
114       returnStatus(request, response, alreadyFinishedString);
115       return;
116     }
117
118     if (getCompletableFuture() == null)
119     {
120       final Map<String, Object> finalObjectMap = objectsPassedToProcessAsync;
121       setCompletableFuture(CompletableFuture.runAsync(() -> {
122         // subclass method
123         try
124         {
125           this.processAsync(request, response, finalObjectMap);
126         } catch (ClassCastException e)
127         {
128           Console.info("Something went wrong with async endpoint execution"
129                   + getName(), e);
130         }
131       }));
132     }
133     addWhenCompleteCompletableFuture();
134
135     // subclass method
136     finalise(request, response);
137
138     returnStatus(response);
139     changeStatus(Status.IN_PROGRESS);
140   }
141
142   protected void atEnd()
143   {
144   }
145
146   protected String finishedResponseString(HttpServletRequest request,
147           HttpServletResponse response)
148   {
149     return null;
150   }
151
152   /*
153    * Shared methods below here
154    */
155
156   protected String setId(HttpServletRequest request, String extension)
157   {
158     String idString = getId(request);
159     Console.debug("GOT ID '" + idString + "'");
160     if (idString == null)
161     {
162       setIdExtension(extension);
163     }
164     else
165     {
166       setId(idString);
167     }
168
169     return getId();
170   }
171
172   protected void changeStatus(Status status)
173   {
174     String id = getId();
175     // don't change a job's status if it has finished or died
176     if (getStatus() == Status.FINISHED || getStatus() == Status.ERROR)
177       return;
178     tempStatus = status;
179     if (status != Status.NOT_RUN)
180       API.getStatusMap().put(id, status);
181   }
182
183   protected Status getStatus()
184   {
185     Status status = API.getStatusMap().get(getId());
186     return status == null ? tempStatus : status;
187   }
188
189   protected void returnStatus(HttpServletResponse response)
190   {
191     returnStatus(null, response, null);
192   }
193
194   protected void returnStatus(HttpServletRequest request,
195           HttpServletResponse response, String message)
196   {
197     String id = getId();
198     try
199     {
200       PrintWriter writer = response.getWriter();
201       if (id != null)
202       {
203         writer.write("id=" + id + "\n");
204       }
205       if (API.getRequestMap().get(id) != null)
206       {
207         writer.write(
208                 "request=" + API.getRequestMap().get(id).toString() + "\n");
209       }
210       if (getStatus() != null)
211       {
212         switch (getStatus())
213         {
214         case STARTED:
215           if (getRequestUrl(request) != null)
216           {
217             response.sendRedirect(getRequestUrl(request));
218             Console.debug(getStatus() + ": redirecting to '"
219                     + getRequestUrl(request) + "'");
220             try
221             {
222               Thread.sleep(1000);
223             } catch (InterruptedException e)
224             {
225             }
226           }
227           else
228           {
229             response.setStatus(HttpServletResponse.SC_ACCEPTED);
230           }
231           break;
232         case IN_PROGRESS:
233           if (getRequestUrl(request) != null)
234           {
235             response.sendRedirect(getRequestUrl(request));
236             Console.debug(getStatus() + ": redirecting to '"
237                     + getRequestUrl(request) + "'");
238             try
239             {
240               Thread.sleep(1500);
241             } catch (InterruptedException e)
242             {
243             }
244           }
245           else
246           {
247             response.setStatus(HttpServletResponse.SC_ACCEPTED);
248           }
249           break;
250         case FINISHED:
251           response.setStatus(HttpServletResponse.SC_CREATED);
252           break;
253         case ERROR:
254           response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
255                   message);
256           break;
257         }
258         writer.write("status=" + getStatus().toString() + "\n");
259       }
260       if (message != null)
261       {
262         writer.write(message);
263       }
264     } catch (IOException e)
265     {
266       Console.debug("Exception writing to REST response", e);
267     }
268   }
269
270   protected boolean checkStatus(HttpServletRequest request,
271           HttpServletResponse response)
272   {
273     return checkStatus(request, response, null);
274   }
275
276   protected boolean checkStatus(HttpServletRequest request,
277           HttpServletResponse response, Status set)
278   {
279     String id = getId();
280     Status status = getStatus();
281     if (status == null)
282     {
283       if (set != null)
284         changeStatus(set);
285       API.getRequestMap().put(id, request.getRequestURI());
286       return false;
287     }
288     else
289     {
290       return true;
291     }
292   }
293
294   protected void addWhenCompleteCompletableFuture()
295   {
296     String id = getId();
297     cf.whenComplete((Void, e) -> {
298       if (e != null)
299       {
300         Console.error("Endpoint job " + id + " did not complete", e);
301         changeStatus(Status.ERROR);
302       }
303       else
304       {
305         Console.info("Endpoint job " + id + " completed successfully");
306         changeStatus(Status.FINISHED);
307         atEnd();
308       }
309     });
310   }
311
312   @Override
313   protected void returnError(HttpServletRequest request,
314           HttpServletResponse response, String message)
315   {
316     changeStatus(Status.NOT_RUN);
317     super.returnError(request, response, message);
318   }
319
320   @Override
321   protected boolean deleteFromCache()
322   {
323     return false;
324   }
325 }