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