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.gui.AlignFrame; 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 removeFromCaches(HttpServletRequest request) { removeFromCaches(getId(request)); } protected void removeFromCaches(String id) { CompletableFuture cf = cfMap.get(id); if (cf == null || cf.isDone()) cfMap.remove(id); AlignFrame.removeFromAlignFrameCache(id); API.getStatusMap().remove(getId()); } 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) this.setId(request, null); // and do this this.saveParameters(request); } protected void saveParameters(HttpServletRequest request) { this.getId(request); this.getFromId(request); this.getEndpointPathParameters(request); this.getQueryParameters(request); this.getOptions(request); this.getRequestUrl(request); } 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); Console.debug("**** STATUS=" + getStatus()); if (checkStatus(request, response, Status.STARTED)) { /* // double check alignframe Console.debug("**** STATUS2=" + getStatus()); if (getStatus().compareTo(Status.IN_PROGRESS) > 0 && getAlignFrameUsingId(request) == null) { Console.debug("**** STATUS3=" + getStatus()); // delete key from cache Console.debug("Cannot find cached AlignFrame for '" + getId() + "', deleting key from cache"); removeFromCaches(getId()); this.changeStatus(null); this.setCompletableFuture(null); } else { */ 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 = getId(request); Console.debug("GOT ID '" + idString + "'"); 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: if (getRequestUrl(request) != null) { response.sendRedirect(getRequestUrl(request)); Console.debug(getStatus() + ": redirecting to '" + getRequestUrl(request) + "'"); try { Thread.sleep(1000); } catch (InterruptedException e) { } } else { response.setStatus(HttpServletResponse.SC_ACCEPTED); } break; case IN_PROGRESS: if (getRequestUrl(request) != null) { response.sendRedirect(getRequestUrl(request)); Console.debug(getStatus() + ": redirecting to '" + getRequestUrl(request) + "'"); try { Thread.sleep(1500); } catch (InterruptedException e) { } } else { 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; } }