eb8280f98d2f5962fb4740196774dedf037af9b1
[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, String> stringsPassedToProcessAsync = new HashMap<>();
32
33   protected void setCompletableFuture(CompletableFuture<Void> cf)
34   {
35     this.cf = cf;
36     if (getId() != null)
37       cfMap.put(getId(), cf);
38   }
39
40   protected CompletableFuture<Void> getCompletableFuture()
41   {
42     if (cf == null && getId() != null && cfMap.get(getId()) != null)
43       cf = cfMap.get(getId());
44     return this.cf;
45   }
46
47   protected void setId(String id)
48   {
49     this.id = id;
50   }
51
52   protected void setIdExtension(String idExtension)
53   {
54     setId(getPath() + "::" + idExtension);
55   }
56
57   protected String getId()
58   {
59     return this.id;
60   }
61
62   /*
63    * Override the three methods
64    * initialise (get parameters, set id (extension), set cf if using an existing one)
65    * process (what to do in the cf if not using an existing one)
66    * finalise (extra stuff to do at the end of the first call to this)
67    */
68   protected void initialise(HttpServletRequest request,
69           HttpServletResponse response)
70   {
71     // should be overridden
72     // must setId(request, extension)
73     setId(request, null);
74   }
75
76   protected abstract void processAsync(HttpServletRequest request,
77           HttpServletResponse response, final Map<String, String> finalMap);
78
79   protected void finalise(HttpServletRequest request,
80           HttpServletResponse response)
81   {
82     // can be Overridden
83   }
84
85   @Override
86   public void processEndpoint(HttpServletRequest request,
87           HttpServletResponse response)
88   {
89     // subclass method
90     initialise(request, response);
91
92     if (checkStatus(request, response, Status.STARTED))
93     {
94       returnStatus(response);
95       return;
96     }
97
98     if (getCompletableFuture() == null)
99     {
100       final Map<String, String> finalMap = stringsPassedToProcessAsync;
101       setCompletableFuture(CompletableFuture.runAsync(() -> {
102         // subclass method
103         this.processAsync(request, response, finalMap);
104       }));
105     }
106     finaliseCompletableFuture();
107
108     // subclass method
109     finalise(request, response);
110
111     returnStatus(response);
112     changeStatus(Status.IN_PROGRESS);
113   }
114
115   protected void atEnd()
116   {
117   }
118
119   /*
120    * Shared methods below here
121    */
122
123   protected String setId(HttpServletRequest request, String extension)
124   {
125     String idString = request.getParameter("id");
126     if (idString == null)
127     {
128       setIdExtension(extension);
129     }
130     else
131     {
132       setId(idString);
133     }
134     return getId();
135   }
136
137   protected void changeStatus(Status status)
138   {
139     String id = getId();
140     // don't change a job's status if it has finished or died
141     if (getStatus() == Status.FINISHED || getStatus() == Status.ERROR)
142       return;
143     getAPI().getStatusMap().put(id, status);
144   }
145
146   protected Status getStatus()
147   {
148     return getAPI().getStatusMap().get(getId());
149   }
150
151   protected void returnStatus(HttpServletResponse response)
152   {
153     String id = getId();
154     try
155     {
156       PrintWriter writer = response.getWriter();
157       if (id != null)
158       {
159         writer.write("id=" + id + "\n");
160       }
161       if (getAPI().getRequestMap().get(id) != null)
162       {
163         writer.write("request="
164                 + getAPI().getRequestMap().get(id).toString() + "\n");
165       }
166       if (getStatus() != null)
167       {
168         if (getStatus() == Status.ERROR)
169         {
170           response.setStatus(500);
171         }
172         writer.write("status=" + getStatus().toString() + "\n");
173       }
174     } catch (IOException e)
175     {
176       Cache.debug(e);
177     }
178   }
179
180   protected boolean checkStatus(HttpServletRequest request,
181           HttpServletResponse response)
182   {
183     return checkStatus(request, response, null);
184   }
185
186   protected boolean checkStatus(HttpServletRequest request,
187           HttpServletResponse response, Status set)
188   {
189     String id = getId();
190     Status status = getStatus();
191     if (status == null)
192     {
193       if (set != null)
194         changeStatus(set);
195       getAPI().getRequestMap().put(id, request.getRequestURI());
196       return false;
197     }
198     else
199     {
200       return true;
201     }
202   }
203
204   protected void finaliseCompletableFuture()
205   {
206     String id = getId();
207     cf.whenComplete((Void, e) -> {
208       if (e != null)
209       {
210         Cache.error("Endpoint job " + id + " did not complete");
211         Cache.debug(e);
212         changeStatus(Status.ERROR);
213       }
214       else
215       {
216         Cache.info("Endpoint job " + id + " completed successfully");
217         changeStatus(Status.FINISHED);
218         atEnd();
219       }
220     });
221   }
222 }