989bd2750296641663d5684c657b829f73435905
[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   }
88
89   protected abstract void processAsync(HttpServletRequest request,
90           HttpServletResponse response, final Map<String, Object> finalMap);
91
92   protected void finalise(HttpServletRequest request,
93           HttpServletResponse response)
94   {
95     // can be Overridden
96   }
97
98   @Override
99   public void processEndpoint(HttpServletRequest request,
100           HttpServletResponse response)
101   {
102     tempStatus = null;
103     // subclass method
104     initialise(request, response);
105
106     if (checkStatus(request, response, Status.STARTED))
107     {
108       String alreadyFinishedString = null;
109       if (getStatus() == Status.FINISHED)
110       {
111         alreadyFinishedString = finishedResponseString(request, response);
112       }
113       returnStatus(request, response, alreadyFinishedString);
114       return;
115     }
116
117     if (getCompletableFuture() == null)
118     {
119       final Map<String, Object> finalObjectMap = objectsPassedToProcessAsync;
120       setCompletableFuture(CompletableFuture.runAsync(() -> {
121         // subclass method
122         try
123         {
124           this.processAsync(request, response, finalObjectMap);
125         } catch (ClassCastException e)
126         {
127           Console.info("Something went wrong with async endpoint execution"
128                   + getName(), e);
129         }
130       }));
131     }
132     addWhenCompleteCompletableFuture();
133
134     // subclass method
135     finalise(request, response);
136
137     returnStatus(response);
138     changeStatus(Status.IN_PROGRESS);
139   }
140
141   protected void atEnd()
142   {
143   }
144
145   protected String finishedResponseString(HttpServletRequest request,
146           HttpServletResponse response)
147   {
148     return null;
149   }
150
151   /*
152    * Shared methods below here
153    */
154
155   protected String setId(HttpServletRequest request, String extension)
156   {
157     String idString = getId(request);
158     Console.debug("GOT ID '" + idString + "'");
159     if (idString == null)
160     {
161       setIdExtension(extension);
162     }
163     else
164     {
165       setId(idString);
166     }
167     return getId();
168   }
169
170   protected void changeStatus(Status status)
171   {
172     String id = getId();
173     // don't change a job's status if it has finished or died
174     if (getStatus() == Status.FINISHED || getStatus() == Status.ERROR)
175       return;
176     tempStatus = status;
177     if (status != Status.NOT_RUN)
178       API.getStatusMap().put(id, status);
179   }
180
181   protected Status getStatus()
182   {
183     Status status = API.getStatusMap().get(getId());
184     return status == null ? tempStatus : status;
185   }
186
187   protected void returnStatus(HttpServletResponse response)
188   {
189     returnStatus(null, response, null);
190   }
191
192   protected void returnStatus(HttpServletRequest request,
193           HttpServletResponse response, String message)
194   {
195     String id = getId();
196     try
197     {
198       PrintWriter writer = response.getWriter();
199       if (id != null)
200       {
201         writer.write("id=" + id + "\n");
202       }
203       if (API.getRequestMap().get(id) != null)
204       {
205         writer.write(
206                 "request=" + API.getRequestMap().get(id).toString() + "\n");
207       }
208       if (getStatus() != null)
209       {
210         switch (getStatus())
211         {
212         case STARTED:
213           response.setStatus(HttpServletResponse.SC_ACCEPTED);
214           break;
215         case IN_PROGRESS:
216           response.setStatus(HttpServletResponse.SC_ACCEPTED);
217           break;
218         case FINISHED:
219           response.setStatus(HttpServletResponse.SC_CREATED);
220           break;
221         case ERROR:
222           response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
223                   message);
224           break;
225         }
226         writer.write("status=" + getStatus().toString() + "\n");
227       }
228       if (message != null)
229       {
230         writer.write(message);
231       }
232     } catch (IOException e)
233     {
234       Console.debug("Exception writing to REST response", e);
235     }
236   }
237
238   protected boolean checkStatus(HttpServletRequest request,
239           HttpServletResponse response)
240   {
241     return checkStatus(request, response, null);
242   }
243
244   protected boolean checkStatus(HttpServletRequest request,
245           HttpServletResponse response, Status set)
246   {
247     String id = getId();
248     Status status = getStatus();
249     if (status == null)
250     {
251       if (set != null)
252         changeStatus(set);
253       API.getRequestMap().put(id, request.getRequestURI());
254       return false;
255     }
256     else
257     {
258       return true;
259     }
260   }
261
262   protected void addWhenCompleteCompletableFuture()
263   {
264     String id = getId();
265     cf.whenComplete((Void, e) -> {
266       if (e != null)
267       {
268         Console.error("Endpoint job " + id + " did not complete", e);
269         changeStatus(Status.ERROR);
270       }
271       else
272       {
273         Console.info("Endpoint job " + id + " completed successfully");
274         changeStatus(Status.FINISHED);
275         atEnd();
276       }
277     });
278   }
279
280   @Override
281   protected void returnError(HttpServletRequest request,
282           HttpServletResponse response, String message)
283   {
284     changeStatus(Status.NOT_RUN);
285     super.returnError(request, response, message);
286   }
287
288   @Override
289   protected boolean deleteFromCache()
290   {
291     return false;
292   }
293 }