+package jalview.rest;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.bin.Console;
+import jalview.rest.RestHandler.Status;
+
+public abstract class AbstractEndpointAsync extends AbstractEndpoint
+{
+ public AbstractEndpointAsync(API api, String path, String name,
+ String parameters, String description)
+ {
+ super(api, path, name, parameters, description);
+ }
+
+ protected String idExtension = null;
+
+ protected String id = null;
+
+ protected CompletableFuture<Void> cf = null;
+
+ protected Map<String, CompletableFuture<Void>> cfMap = new HashMap<>();
+
+ protected Map<String, Object> objectsPassedToProcessAsync = new HashMap<>();
+
+ private Status tempStatus = null;
+
+ protected void setCompletableFuture(CompletableFuture<Void> cf)
+ {
+ this.cf = cf;
+ if (getId() != null)
+ cfMap.put(getId(), cf);
+ }
+
+ protected CompletableFuture<Void> getCompletableFuture()
+ {
+ if (cf == null && getId() != null && cfMap.get(getId()) != null)
+ cf = cfMap.get(getId());
+ return this.cf;
+ }
+
+ protected void setId(String id)
+ {
+ this.id = id;
+ }
+
+ protected void setIdExtension(String idExtension)
+ {
+ setId(getPath() + "::" + idExtension);
+ }
+
+ protected String getId()
+ {
+ return this.id;
+ }
+
+ /*
+ * Override the three methods
+ * initialise (get parameters, set id (extension), set cf if using an existing one)
+ * process (what to do in the cf if not using an existing one)
+ * finalise (extra stuff to do at the end of the first call to this)
+ */
+ protected void initialise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ // should be overridden
+ // must setId(request, extension)
+ setId(request, null);
+ }
+
+ protected abstract void processAsync(HttpServletRequest request,
+ HttpServletResponse response, final Map<String, Object> finalMap);
+
+ protected void finalise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ // can be Overridden
+ }
+
+ @Override
+ public void processEndpoint(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ tempStatus = null;
+ // subclass method
+ initialise(request, response);
+
+ if (checkStatus(request, response, Status.STARTED))
+ {
+ String alreadyFinishedString = null;
+ if (getStatus() == Status.FINISHED)
+ {
+ alreadyFinishedString = finishedResponseString(request, response);
+ }
+ returnStatus(request, response, alreadyFinishedString);
+ return;
+ }
+
+ if (getCompletableFuture() == null)
+ {
+ final Map<String, Object> finalObjectMap = objectsPassedToProcessAsync;
+ setCompletableFuture(CompletableFuture.runAsync(() -> {
+ // subclass method
+ try
+ {
+ this.processAsync(request, response, finalObjectMap);
+ } catch (ClassCastException e)
+ {
+ Console.info("Something went wrong with async endpoint execution"
+ + getName(), e);
+ }
+ }));
+ }
+ addWhenCompleteCompletableFuture();
+
+ // subclass method
+ finalise(request, response);
+
+ returnStatus(response);
+ changeStatus(Status.IN_PROGRESS);
+ }
+
+ protected void atEnd()
+ {
+ }
+
+ protected String finishedResponseString(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ return null;
+ }
+
+ /*
+ * Shared methods below here
+ */
+
+ protected String setId(HttpServletRequest request, String extension)
+ {
+ String idString = request.getParameter("id");
+ if (idString == null)
+ {
+ setIdExtension(extension);
+ }
+ else
+ {
+ setId(idString);
+ }
+ return getId();
+ }
+
+ protected void changeStatus(Status status)
+ {
+ String id = getId();
+ // don't change a job's status if it has finished or died
+ if (getStatus() == Status.FINISHED || getStatus() == Status.ERROR)
+ return;
+ tempStatus = status;
+ if (status != Status.NOT_RUN)
+ API.getStatusMap().put(id, status);
+ }
+
+ protected Status getStatus()
+ {
+ Status status = API.getStatusMap().get(getId());
+ return status == null ? tempStatus : status;
+ }
+
+ protected void returnStatus(HttpServletResponse response)
+ {
+ returnStatus(null, response, null);
+ }
+
+ protected void returnStatus(HttpServletRequest request,
+ HttpServletResponse response, String message)
+ {
+ String id = getId();
+ try
+ {
+ PrintWriter writer = response.getWriter();
+ if (id != null)
+ {
+ writer.write("id=" + id + "\n");
+ }
+ if (API.getRequestMap().get(id) != null)
+ {
+ writer.write(
+ "request=" + API.getRequestMap().get(id).toString() + "\n");
+ }
+ if (getStatus() != null)
+ {
+ switch (getStatus())
+ {
+ case STARTED:
+ response.setStatus(HttpServletResponse.SC_ACCEPTED);
+ break;
+ case IN_PROGRESS:
+ response.setStatus(HttpServletResponse.SC_ACCEPTED);
+ break;
+ case FINISHED:
+ response.setStatus(HttpServletResponse.SC_CREATED);
+ break;
+ case ERROR:
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ message);
+ break;
+ }
+ writer.write("status=" + getStatus().toString() + "\n");
+ }
+ if (message != null)
+ {
+ writer.write(message);
+ }
+ } catch (IOException e)
+ {
+ Console.debug("Exception writing to REST response", e);
+ }
+ }
+
+ protected boolean checkStatus(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ return checkStatus(request, response, null);
+ }
+
+ protected boolean checkStatus(HttpServletRequest request,
+ HttpServletResponse response, Status set)
+ {
+ String id = getId();
+ Status status = getStatus();
+ if (status == null)
+ {
+ if (set != null)
+ changeStatus(set);
+ API.getRequestMap().put(id, request.getRequestURI());
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ protected void addWhenCompleteCompletableFuture()
+ {
+ String id = getId();
+ cf.whenComplete((Void, e) -> {
+ if (e != null)
+ {
+ Console.error("Endpoint job " + id + " did not complete", e);
+ changeStatus(Status.ERROR);
+ }
+ else
+ {
+ Console.info("Endpoint job " + id + " completed successfully");
+ changeStatus(Status.FINISHED);
+ atEnd();
+ }
+ });
+ }
+
+ @Override
+ protected void returnError(HttpServletRequest request,
+ HttpServletResponse response, String message)
+ {
+ changeStatus(Status.NOT_RUN);
+ super.returnError(request, response, message);
+ }
+
+ @Override
+ protected boolean deleteFromCache()
+ {
+ return false;
+ }
+}