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 cf = null; protected Map> cfMap = new HashMap<>(); protected Map objectsPassedToProcessAsync = new HashMap<>(); private Status tempStatus = null; protected void setCompletableFuture(CompletableFuture cf) { this.cf = cf; if (getId() != null) cfMap.put(getId(), cf); } protected CompletableFuture 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 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 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; } }