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