--- /dev/null
+package jalview.rest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.bin.Cache;
+import jalview.rest.RestHandler.EndpointI;
+
+public abstract class Endpoint implements EndpointI
+{
+ private String name = null;
+
+ private API api = null;
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return this.name;
+ }
+
+ protected API getAPI()
+ {
+ return this.api;
+ }
+
+ public abstract void processEndpoint(HttpServletRequest request,
+ HttpServletResponse response);
+
+ /*
+ * Shared methods below here
+ */
+
+ protected String[] getEndpointPathParameters(HttpServletRequest request)
+ {
+ String pathInfo = request.getPathInfo();
+ int slashpos = pathInfo.indexOf('/', 1);
+ return slashpos < 1 ? null
+ : pathInfo.substring(slashpos + 1).split("/");
+ }
+
+ protected void returnError(HttpServletRequest request,
+ HttpServletResponse response, String message)
+ {
+ response.setStatus(500); // set this to something better
+ String endpointName = getName();
+ Cache.error(getAPI().getName() + " error: endpoint " + endpointName
+ + " failed: '" + message + "'");
+ try
+ {
+ PrintWriter writer = response.getWriter();
+ writer.write("Endpoint " + endpointName + ": " + message);
+ writer.close();
+ } catch (IOException e)
+ {
+ Cache.debug(e);
+ }
+ }
+
+ protected String getRequestBody(HttpServletRequest request)
+ throws IOException
+ {
+ StringBuilder sb = new StringBuilder();
+ BufferedReader reader = request.getReader();
+ try
+ {
+ String line;
+ while ((line = reader.readLine()) != null)
+ {
+ sb.append(line).append('\n');
+ }
+ } finally
+ {
+ reader.close();
+ }
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package jalview.rest;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.CompletableFuture;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.bin.Cache;
+import jalview.rest.RestHandler.Status;
+
+public abstract class EndpointAsync extends Endpoint
+{
+ private String name = null;
+
+ private String idExtension = null;
+
+ private String id = null;
+
+ private CompletableFuture<Void> cf = null;
+
+ protected void setCompletableFuture(CompletableFuture<Void> cf)
+ {
+ this.cf = cf;
+ }
+
+ protected CompletableFuture<Void> getCompletableFuture()
+ {
+ return this.cf;
+ }
+
+ protected void setId(String idExtension)
+ {
+ this.id = getName() + "::" + 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 process(HttpServletRequest request,
+ HttpServletResponse response);
+
+ protected void finalise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ // can be Overridden
+ }
+
+ protected void passCompletableFuture()
+ {
+ // Override this if you want to use an existing CompletableFuture
+ }
+
+ @Override
+ public void processEndpoint(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ initialise(request, response);
+ final String id = getId();
+
+ if (checkStatus(request, response))
+ return;
+
+ passCompletableFuture();
+ if (getCompletableFuture() == null)
+ {
+ setCompletableFuture(CompletableFuture.runAsync(() -> {
+ this.process(request, response);
+ }));
+ }
+ finaliseCompletableFuture();
+
+ finalise(request, response);
+
+ returnStatus(response);
+ changeStatus(Status.IN_PROGRESS);
+ }
+
+ /*
+ * Shared methods below here
+ */
+
+ protected String setId(HttpServletRequest request, String extension)
+ {
+ String idString = request.getParameter("id");
+ if (idString == null)
+ {
+ setId(extension);
+ idString = getId();
+ }
+ return idString;
+ }
+
+ protected void changeStatus(Status status)
+ {
+ String id = getId();
+ // don't change a job's status if it has finished or died
+ if (API.getStatusMap().get(id) == Status.FINISHED
+ || API.getStatusMap().get(id) == Status.ERROR)
+ return;
+ API.getStatusMap().put(id, status);
+ }
+
+ protected Status getStatus()
+ {
+ return API.getStatusMap().get(getId());
+ }
+
+ protected void returnStatus(HttpServletResponse response)
+ {
+ 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 (API.getStatusMap().get(id) != null)
+ {
+ if (API.getStatusMap().get(id) == Status.ERROR)
+ response.setStatus(500);
+ writer.write(
+ "status=" + API.getStatusMap().get(id).toString() + "\n");
+ }
+ } catch (IOException e)
+ {
+ Cache.debug(e);
+ }
+ }
+
+ protected boolean checkStatus(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ String id = getId();
+ Status status = API.getStatusMap().get(id);
+ if (status == null)
+ {
+ API.getStatusMap().put(id, Status.STARTED);
+ API.getRequestMap().put(id, request.getRequestURI());
+ }
+ else
+ {
+ returnStatus(response);
+ return true;
+ }
+ return false;
+ }
+
+ protected void finaliseCompletableFuture()
+ {
+ String id = getId();
+ cf.whenComplete((Void, e) -> {
+ if (e != null)
+ {
+ Cache.error("Endpoint job " + id + " did not complete");
+ Cache.debug(e);
+ changeStatus(Status.ERROR);
+ }
+ else
+ {
+ Cache.info("Endpoint job " + id + " completed successfully");
+ changeStatus(Status.FINISHED);
+ }
+ });
+ }
+}
--- /dev/null
+package jalview.rest;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.gui.Desktop;
+import jalview.gui.SequenceFetcher;
+import jalview.util.DBRefUtils;
+
+public class FetchSequenceEndpoint extends EndpointAsync
+{
+ protected String name = "fetchsequence";
+
+ @Override
+ protected void initialise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ // note that endpointName should always be "fetchsequence"
+
+ String[] parameters = getEndpointPathParameters(request);
+
+ // check we can run fetchsequence
+ if (parameters.length < 2)
+ {
+ returnError(request, response,
+ "requires 2 path parameters: dbname, ids");
+ return;
+ }
+
+ String dbName = parameters[0];
+ String dbId = parameters[1];
+
+ setId(request, dbName + "::" + dbId);
+ if (checkStatus(request, response))
+ return;
+
+ String db = DBRefUtils.getCanonicalName(dbName);
+ Desktop desktop = Desktop.instance;
+ SequenceFetcher sf = new SequenceFetcher(desktop, db, dbId);
+ setCompletableFuture(sf.ok_actionPerformed(true));
+ }
+
+ protected void process(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ // all the work being done by the SequenceFetcher!
+ }
+}
--- /dev/null
+package jalview.rest;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.bind.DatatypeConverter;
+
+import jalview.bin.Cache;
+import jalview.gui.CutAndPasteTransfer;
+import jalview.gui.Desktop;
+
+public class OpenSequenceEndpoint extends EndpointAsync
+{
+ protected String name = "opensequence";
+
+ private String fileString;
+
+ private String urlString;
+
+ private String method;
+
+ private String dataString;
+
+ private String body;
+
+ private String content;
+
+ private boolean post;
+
+ private boolean data;
+
+ String access = null;
+
+ String ref = null;
+
+ @Override
+ protected void initialise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ fileString = request.getParameter("file");
+ urlString = request.getParameter("url");
+ method = request.getMethod().toLowerCase();
+ dataString = request.getParameter("data");
+ body = null;
+ post = method.equalsIgnoreCase("post");
+ data = dataString != null;
+
+ if (post)
+ {
+ access = "post";
+ try
+ {
+ body = getRequestBody(request);
+ } catch (IOException e)
+ {
+ returnError(request, response, "could not read POST body");
+ Cache.debug(e);
+ return;
+ }
+ // for ref see md5 later
+ }
+ else if (data)
+ {
+ access = "data";
+ // for ref see md5 later
+ }
+ else if (fileString != null)
+ {
+ access = "file";
+ ref = fileString;
+ }
+ else if (urlString != null)
+ {
+ access = "url";
+ ref = urlString;
+ }
+
+ if (access == null)
+ {
+ returnError(request, response,
+ "requires POST body or one of parameters 'data', 'file' or 'url'");
+ return;
+ }
+
+ // final content used in Future
+ final String content;
+ if (post || data)
+ {
+ content = post ? body : dataString;
+ try
+ {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(content.getBytes());
+ byte[] digest = md5.digest();
+ ref = DatatypeConverter.printBase64Binary(digest).toLowerCase();
+ } catch (NoSuchAlgorithmException e)
+ {
+ Cache.debug(e);
+ }
+ }
+ else
+ {
+ content = null;
+ }
+
+ setId(request, access + "::" + ref);
+ }
+
+ protected void process(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ if (post || data)
+ {
+ // Sequence file contents being posted
+ // use File -> Input Alignment -> from Textbox
+ CutAndPasteTransfer cap = new CutAndPasteTransfer();
+ cap.setText(content);
+ cap.ok_actionPerformed(null);
+ cap.cancel_actionPerformed(null);
+ }
+ else if (fileString != null)
+ {
+ // Sequence file on filesystem
+ // use File -> Input Alignment -> From File
+ URL url = null;
+ File file = null;
+ try
+ {
+ url = new URL(fileString);
+ file = new File(url.toURI());
+ } catch (MalformedURLException | URISyntaxException e)
+ {
+ returnError(request, response,
+ "could not resolve file='" + fileString + "'");
+ Cache.debug(e);
+ return;
+ }
+ if (!file.exists())
+ {
+ returnError(request, response,
+ "file='" + fileString + "' does not exist");
+ return;
+ }
+ Desktop.instance.openFile(file, null, null);
+ }
+ else if (urlString != null)
+ {
+ boolean success = Desktop.instance.loadUrl(urlString, null);
+ if (!success)
+ {
+ returnError(request, response,
+ "url='" + urlString + "' could not be opened");
+ return;
+ }
+ }
+ }
+}