--- /dev/null
+# Jalview Rest(-like) API
+
+## Launch Jalview with API active on specified port
+
+`java -jar jalview_all.jar -nosplash -nowebservicediscovery -startapi -serverport 2021`
+(-nosplash and -nowebservicediscovery just to save time/output)
+
+## Open a MSA window with existing data
+
+`*POST* http://localhost:2021/jalview/api/inputalignment`
+with body of HTTP request being content of file (automatic parsing by Jalview)
+
+`*GET* http://localhost:2021/jalview/api/inputalignment?data=<data>`
+with <data> being content of file (limited size)
+
+`*GET* http://localhost:2021/jalview/api/inputalignment?file=<fileURI>`
+with <fileURI> being a file
+
+`*GET* http://localhost:2021/jalview/api/inputalignment?url=<URL>`
+with <URL> being a URL to a file
+
+## Open a MSA window with online resource fetch
+
+`http://localhost:2021/jalview/api/fetchsequences/<resource name>/<sequence ids>`
+<resource name> is one of "ensemble", "pdb", "ensembl-tr", "ensembl-gn", ["uniprotkb/swiss-prot", "uniprotkb/trembl", ""uniprot/sptrembl", "uniprot/swissprot" not yet implemented]
+<sequence ids> ids for fetch
+
+### optional parameters
+`id=<your specified id for this request>`
+
+### Returns (as key=value pairs in the body of the response)
+```
+id=<the given id for this request>
+status=<STARTED|IN_PROGRESS|FINISHED|ERROR|NOT_RUN>
+
+
+
+
+
+
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
+import java.net.BindException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import jalview.gui.AlignFrame;
import jalview.gui.Desktop;
import jalview.gui.PromptUserConfig;
+import jalview.httpserver.HttpServer;
import jalview.io.AppletFormatAdapter;
import jalview.io.BioJsHTMLOutput;
import jalview.io.DataSourceType;
import jalview.io.IdentifyFile;
import jalview.io.NewickFile;
import jalview.io.gff.SequenceOntologyFactory;
+import jalview.rest.API;
import jalview.schemes.ColourSchemeI;
import jalview.schemes.ColourSchemeProperty;
import jalview.util.ChannelProperties;
}
}
+ // set the jetty port if suggested
+ String sPort = aparser.getValue("serverport");
+ if (sPort != null)
+ {
+ int port = 0;
+ try
+ {
+ port = Integer.parseInt(sPort);
+ HttpServer.setSuggestedPort(port);
+ Console.info("Set suggested server port to " + port);
+ } catch (NumberFormatException e)
+ {
+ Console.warn(
+ "server_port '" + sPort + "' not parseable as Integer");
+ }
+ }
+ // Start a TestListener
+ if (aparser.contains("startapi"))
+ {
+ try
+ {
+ API api = API.getInstance();
+ Console.info(api.getName() + " started at "
+ + HttpServer.getInstance().getUri().toString());
+ } catch (BindException e)
+ {
+ Console.warn("Could not open a genomeapi", e);
+ }
+ }
+
// Move any new getdown-launcher-new.jar into place over old
// getdown-launcher.jar
String appdirString = System.getProperty("getdownappdir");
import java.util.Arrays;
import java.util.Deque;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
+import java.util.Map;
import java.util.Vector;
+import java.util.concurrent.CompletableFuture;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
return showp;
}
+ public List<String> getProducts()
+ {
+ SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
+ AlignmentI dataset = viewport.getAlignment().getDataset();
+
+ boolean dna = viewport.getAlignment().isNucleotide();
+
+ if (seqs == null || seqs.length == 0)
+ {
+ // nothing to see here.
+ return null;
+ }
+
+ return new CrossRef(seqs, dataset).findXrefSourcesForSequences(dna);
+ }
+
/**
* Finds and displays cross-references for the selected sequences (protein
* products for nucleotide sequences, dna coding sequences for peptides).
* @param source
* the database to show cross-references for
*/
+ /*
protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
final String source)
{
new Thread(CrossRefAction.getHandlerFor(sel, _odna, source, this))
.start();
}
+ */
+ protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
+ final String source)
+ {
+ showProductsFor(sel, _odna, source, false, null);
+ }
+
+ public CompletableFuture<Void> showProductsFor(final SequenceI[] sel,
+ final boolean _odna, final String source, boolean returnFuture,
+ String id)
+ {
+ CompletableFuture<Void> cf = CompletableFuture
+ .runAsync(() -> runCrossRefActionAndCacheAlignFrame(sel, _odna,
+ source, returnFuture, id));
+ return returnFuture ? cf : null;
+ }
+
+ private void runCrossRefActionAndCacheAlignFrame(SequenceI[] sel,
+ boolean _odna, String source, boolean cacheAlignFrame, String id)
+ {
+ final AlignFrame af = this;
+ CrossRefAction.getHandlerFor(sel, _odna, source, af).run();
+ af.cacheAlignFrameFromRestId(id);
+ }
/**
* Construct and display a new frame containing the translation of this
}
}
}
+
});
}
}).start();
{
return lastFeatureSettingsBounds;
}
+
+ /*
+ * Caching hashmaps for jalview.rest.API
+ */
+ private static Map<String, AlignFrame> alignFrameMap = null;
+
+ public static AlignFrame getAlignFrameFromRestId(String id)
+ {
+ if (id == null || alignFrameMap == null)
+ return null;
+ return alignFrameMap.get(id);
+ }
+
+ public void cacheAlignFrameFromRestId(String id)
+ {
+ if (id == null)
+ return;
+ if (alignFrameMap == null)
+ alignFrameMap = new HashMap<>();
+ alignFrameMap.put(id, this);
+ }
}
class PrintThread extends Thread
*/
package jalview.gui;
-import java.util.Locale;
-
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.util.Hashtable;
import java.util.List;
import java.util.ListIterator;
+import java.util.Locale;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
showMemusage.setSelected(selmemusage);
desktop.setBackground(Color.white);
+ this.setIconImages(ChannelProperties.getIconList());
+
getContentPane().setLayout(new BorderLayout());
// alternate config - have scrollbars - see notes in JAL-153
// JScrollPane sp = new JScrollPane();
frame.setIcon(false);
} catch (java.beans.PropertyVetoException ex)
{
-
+ // System.err.println(ex.toString());
}
}
});
: protocols.get(i);
FileFormatI format = null;
- if (fileName.endsWith(".jar"))
+ if (fileName.toLowerCase(Locale.ROOT).endsWith(".jar"))
{
format = FileFormat.Jalview;
Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
FileFormatI format = chooser.getSelectedFormat();
-
- /*
- * Call IdentifyFile to verify the file contains what its extension implies.
- * Skip this step for dynamically added file formats, because IdentifyFile does
- * not know how to recognise them.
- */
- if (FileFormats.getInstance().isIdentifiable(format))
- {
- try
- {
- format = new IdentifyFile().identify(selectedFile,
- DataSourceType.FILE);
- } catch (FileFormatException e)
- {
- // format = null; //??
- }
- }
-
- new FileLoader().LoadFile(viewport, selectedFile,
- DataSourceType.FILE, format);
+ openFile(selectedFile, format, viewport);
}
});
chooser.showOpenDialog(this);
}
+ public void openFile(File selectedFile, FileFormatI format,
+ AlignViewport viewport)
+ {
+
+ /*
+ * Call IdentifyFile to verify the file contains what its extension implies.
+ * Skip this step for dynamically added file formats, because
+ * IdentifyFile does not know how to recognise them.
+ */
+ if (FileFormats.getInstance().isIdentifiable(format))
+ {
+ try
+ {
+ format = new IdentifyFile().identify(selectedFile,
+ DataSourceType.FILE);
+ } catch (FileFormatException e)
+ {
+ // format = null; //??
+ }
+ }
+
+ new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE,
+ format);
+ }
+
/**
* Shows a dialog for input of a URL at which to retrieve alignment data
*
: ((JComboBox<String>) history).getEditor().getItem()
.toString().trim());
- if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
- {
- if (viewport != null)
- {
- new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
- FileFormat.Jalview);
- }
- else
- {
- new FileLoader().LoadFile(url, DataSourceType.URL,
- FileFormat.Jalview);
- }
- }
- else
+ if (!loadUrl(url, viewport))
{
- FileFormatI format = null;
- try
- {
- format = new IdentifyFile().identify(url, DataSourceType.URL);
- } catch (FileFormatException e)
- {
- // TODO revise error handling, distinguish between
- // URL not found and response not valid
- }
-
- if (format == null)
- {
- String msg = MessageManager
- .formatMessage("label.couldnt_locate", url);
- JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
- MessageManager.getString("label.url_not_found"),
- JvOptionPane.WARNING_MESSAGE);
-
- return;
- }
-
- if (viewport != null)
- {
- new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
- format);
- }
- else
- {
- new FileLoader().LoadFile(url, DataSourceType.URL, format);
- }
+ String msg = MessageManager.formatMessage("label.couldnt_locate",
+ url);
+ JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
+ MessageManager.getString("label.url_not_found"),
+ JvOptionPane.WARNING_MESSAGE);
}
}
};
MessageManager.getString("action.ok"));
}
+ public boolean loadUrl(String url, AlignViewport viewport)
+ {
+ if (url.toLowerCase(Locale.ROOT).endsWith(".jar"))
+ {
+ if (viewport != null)
+ {
+ new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+ FileFormat.Jalview);
+ }
+ else
+ {
+ new FileLoader().LoadFile(url, DataSourceType.URL,
+ FileFormat.Jalview);
+ }
+ }
+ else
+ {
+ FileFormatI format = null;
+ try
+ {
+ format = new IdentifyFile().identify(url, DataSourceType.URL);
+ } catch (FileFormatException e)
+ {
+ // TODO revise error handling, distinguish between
+ // URL not found and response not valid
+ }
+
+ if (format == null)
+ {
+
+ return false;
+ }
+
+ if (viewport != null)
+ {
+ new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
+ format);
+ }
+ else
+ {
+ new FileLoader().LoadFile(url, DataSourceType.URL, format);
+ }
+ }
+ return true;
+ }
+
/**
* Opens the CutAndPaste window for the user to paste an alignment in to
*
Transferable t) throws Exception
{
+ // BH 2018 changed List<String> to List<Object> to allow for File from
+ // SwingJS
+
+ // DataFlavor[] flavors = t.getTransferDataFlavors();
+ // for (int i = 0; i < flavors.length; i++) {
+ // if (flavors[i].isFlavorJavaFileListType()) {
+ // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ // List<File> list = (List<File>) t.getTransferData(flavors[i]);
+ // for (int j = 0; j < list.size(); j++) {
+ // File file = (File) list.get(j);
+ // byte[] data = getDroppedFileBytes(file);
+ // fileName.setText(file.getName() + " - " + data.length + " " +
+ // evt.getLocation());
+ // JTextArea target = (JTextArea) ((DropTarget)
+ // evt.getSource()).getComponent();
+ // target.setText(new String(data));
+ // }
+ // dtde.dropComplete(true);
+ // return;
+ // }
+ //
+
DataFlavor uriListFlavor = new DataFlavor(
"text/uri-list;class=java.lang.String"), urlFlavour = null;
try
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import javax.swing.JButton;
import javax.swing.JCheckBox;
* @param guiIndic
* @param selectedDb
* @param queryString
+ * @param interactive
*/
public SequenceFetcher(IProgressIndicator guiIndic,
final String selectedDb, final String queryString)
{
+ this(guiIndic, selectedDb, queryString, true);
+ }
+
+ public SequenceFetcher(IProgressIndicator guiIndic,
+ final String selectedDb, final String queryString,
+ boolean interactive)
+ {
this.progressIndicator = guiIndic;
getSequenceFetcherSingleton();
this.guiWindow = progressIndicator;
alignFrame = (AlignFrame) progressIndicator;
}
- jbInit(selectedDb);
+ jbInit(selectedDb, interactive);
textArea.setText(queryString);
frame = new JInternalFrame();
.getString("label.additional_sequence_fetcher"));
}
- private void jbInit(String selectedDb)
+ private void jbInit(String selectedDb, boolean interactive)
{
this.setLayout(new BorderLayout());
jScrollPane1.getViewport().add(textArea);
idsPanel.add(jScrollPane1, BorderLayout.CENTER);
+ // En/disable or show/hide interactive elements
+ database.setEnabled(interactive);
+ exampleAccession.setVisible(interactive);
+ replacePunctuation.setVisible(interactive);
+ okBtn.setVisible(interactive);
+ exampleBtn.setVisible(interactive);
+ closeBtn.setVisible(interactive);
+ backBtn.setVisible(interactive);
+ jLabel1.setVisible(interactive);
+ clear.setVisible(interactive);
+ textArea.setEnabled(interactive);
+
this.add(actionPanel, BorderLayout.SOUTH);
this.add(idsPanel, BorderLayout.CENTER);
this.add(databasePanel, BorderLayout.NORTH);
*
* @param e
*/
- protected void close_actionPerformed(ActionEvent e)
+ public void close_actionPerformed(ActionEvent e)
{
try
{
*/
public void ok_actionPerformed()
{
+ ok_actionPerformed(false, null);
+ }
+
+ public CompletableFuture<Void> ok_actionPerformed(boolean returnFuture,
+ String id)
+ {
/*
* tidy inputs and check there is something to search for
*/
showErrorMessage(
"Please enter a (semi-colon separated list of) database id(s)");
resetDialog();
- return;
+ return null;
}
if (database.getSelectedIndex() == 0)
{
// todo i18n
showErrorMessage("Please choose a database");
resetDialog();
- return;
+ return null;
}
exampleBtn.setEnabled(false);
closeBtn.setEnabled(false);
backBtn.setEnabled(false);
- Thread worker = new Thread(this);
- worker.start();
+ CompletableFuture<Void> worker = CompletableFuture
+ .runAsync(() -> runAndCacheAlignFrame(returnFuture, id));
+
+ return returnFuture ? worker : null;
+ }
+
+ private void runAndCacheAlignFrame(boolean cacheAlignFrame, String id)
+ {
+ AlignFrame af = this.run(cacheAlignFrame);
+ if (cacheAlignFrame && id != null && af != null)
+ af.cacheAlignFrameFromRestId(id);
}
private void resetDialog()
@Override
public void run()
{
+ run(false);
+ }
+
+ public AlignFrame run(boolean returnAlignFrame)
+ {
boolean addToLast = false;
List<String> aresultq = new ArrayList<>();
List<String> presultTitle = new ArrayList<>();
: MessageManager.getString("status.processing"),
Thread.currentThread().hashCode());
// process results
+ AlignFrame af = null;
while (presult.size() > 0)
{
- parseResult(presult.remove(0), presultTitle.remove(0), null,
+ af = parseResult(presult.remove(0), presultTitle.remove(0), null,
preferredFeatureColours);
}
// only remove visual delay after we finished parsing.
showErrorMessage(sb.toString());
}
resetDialog();
+ return returnAlignFrame ? af : null;
}
/**
* @param preferredFeatureColours
* @return the alignment
*/
- public AlignmentI parseResult(AlignmentI al, String title,
+ public AlignFrame parseResult(AlignmentI al, String title,
FileFormatI currentFileFormat,
FeatureSettingsModelI preferredFeatureColours)
{
+ AlignFrame af = alignFrame;
if (al != null && al.getHeight() > 0)
{
if (title == null)
{
title = getDefaultRetrievalTitle();
}
- if (alignFrame == null)
+ if (af == null)
{
- AlignFrame af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
+ af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
AlignFrame.DEFAULT_HEIGHT);
if (currentFileFormat != null)
{
}
else
{
- alignFrame.viewport.addAlignment(al, title);
+ af.viewport.addAlignment(al, title);
}
}
- return al;
+ return af;
}
void showErrorMessage(final String error)
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import jalview.bin.Console;
+
/**
*
* @author gmcarstairs
/*
* Set server error status on response
*/
- System.err.println("Exception handling request "
- + request.getRequestURI() + " : " + t.getMessage());
+ Console.error("Exception handling request " + request.getRequestURI(),
+ t);
if (response.isCommitted())
{
/*
*/
package jalview.httpserver;
-import jalview.rest.RestHandler;
-
import java.net.BindException;
import java.net.URI;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import jalview.rest.RestHandler;
+
/**
* An HttpServer built on Jetty. To use it
* <ul>
*/
private URI contextRoot;
+ /*
+ * The port of the server. This can be set before starting the instance
+ * as a suggested port to use (it is not guaranteed).
+ * The value will be set to the actual port being used after the instance
+ * is started.
+ */
+ private static int PORT = 0;
+
/**
* Returns the singleton instance of this class.
*
try
{
/*
- * Create a server with a small number of threads; jetty will allocate a
- * free port
+ * Create a server with a small number of threads;
+ * If PORT has been set then jetty will try and use this, otherwise
+ * jetty will allocate a free port
*/
QueuedThreadPool tp = new QueuedThreadPool(4, 1); // max, min
server = new Server(tp);
ServerConnector connector = new ServerConnector(server, 0, 2);
// restrict to localhost
connector.setHost("localhost");
+ if (PORT > 0)
+ {
+ connector.setPort(PORT);
+ }
server.addConnector(connector);
/*
contextHandlers = new HandlerCollection(true);
server.setHandler(contextHandlers);
server.start();
+ Connector[] cs = server.getConnectors();
+ if (cs.length > 0)
+ {
+ if (cs[0] instanceof ServerConnector)
+ {
+ ServerConnector c = (ServerConnector) cs[0];
+ PORT = c.getPort();
+ }
+ }
// System.out.println(String.format(
// "HttpServer started with %d threads", server.getThreadPool()
// .getThreads()));
+ " handler on " + handler.getUri());
}
}
+
+ /**
+ * Gets the ContextHandler attached to this handler. Useful for obtaining the
+ * full path used to access a given handler.
+ *
+ * @param handler
+ */
+ public ContextHandler getContextHandler(AbstractRequestHandler handler)
+ {
+ return myHandlers.get(handler);
+ }
+
+ /**
+ * This sets the "suggested" port to use. It can only be called once before
+ * starting the HttpServer instance. After the server has actually started the
+ * port is set to the actual port being used and cannot be changed.
+ *
+ * @param port
+ * @return successful change
+ */
+ public static boolean setSuggestedPort(int port)
+ {
+ if (port < 1 || PORT > 0)
+ {
+ return false;
+ }
+ PORT = port;
+ return true;
+ }
+
+ public static int getPort()
+ {
+ return PORT;
+ }
}
--- /dev/null
+package jalview.rest;
+
+import java.net.BindException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class API extends RestHandler
+{
+ private static final String MY_PATH = "api";
+
+ private static final String MY_NAME = "Jalview API";
+
+ private static Map<String, Status> statusMap = new HashMap<>();
+
+ private static Map<String, String> requestMap = new HashMap<>();
+
+ private static Map<String, Object> objectMap = new HashMap<>();
+
+ private static API instance = null;
+
+ public static API getInstance() throws BindException
+ {
+ synchronized (API.class)
+ {
+ if (instance == null)
+ {
+ instance = new API();
+ }
+ }
+ return instance;
+ }
+
+ private API() throws BindException
+ {
+ super();
+ }
+
+ private boolean init = false;
+
+ @Override
+ protected void init() throws BindException
+ {
+ if (init)
+ return;
+ super.init(MY_PATH);
+
+ // add endpoints here
+ addEndpoint(new FetchSequencesEndpoint(this));
+ addEndpoint(new InputAlignmentEndpoint(this));
+ addEndpoint(new HighlightSequenceEndpoint(this));
+ addEndpoint(new SelectSequencesEndpoint(this));
+ addEndpoint(new GetCrossReferencesEndpoint(this));
+
+ setPath(MY_PATH);
+ this.registerHandler();
+
+ init = true;
+ }
+
+ /*
+ * Shared methods below here
+ */
+
+ @Override
+ public String getPath()
+ {
+ return MY_PATH;
+ }
+
+ protected static Map<String, Status> getStatusMap()
+ {
+ return statusMap;
+ }
+
+ protected static Map<String, String> getRequestMap()
+ {
+ return requestMap;
+ }
+
+ protected static Map<String, Object> getObjectMap()
+ {
+ return objectMap;
+ }
+}
--- /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.Console;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.rest.RestHandler.EndpointI;
+
+public abstract class AbstractEndpoint implements EndpointI
+{
+ private final String path;
+
+ private API api;
+
+ private final String name;
+
+ private final String parameters;
+
+ private final String description;
+
+ public AbstractEndpoint(API api, String path, String name,
+ String parameters, String description)
+ {
+ this.api = api;
+ this.path = path;
+ this.name = name;
+ this.parameters = parameters;
+ this.description = description;
+ }
+
+ public String getPath()
+ {
+ return this.path;
+ }
+
+ protected API getAPI()
+ {
+ return this.api;
+ }
+
+ public String getName()
+ {
+ return this.name;
+ }
+
+ public String getParameters()
+ {
+ return this.parameters;
+ }
+
+ public String getDescription()
+ {
+ return this.description;
+ }
+
+ 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)
+ {
+ String okString = request.getParameter("ok");
+ boolean ok = (okString != null && okString.equalsIgnoreCase("true"));
+ /*
+ * Annoyingly jetty is not adding content to anything other than a few
+ * 20x status codes. Possibly it is closing the PrintWriter.
+ * Find a fix for this!
+ ****************************************************/
+ response.setStatus(ok ? HttpServletResponse.SC_OK : //
+ // HttpServletResponse.SC_BAD_REQUEST //
+ HttpServletResponse.SC_PARTIAL_CONTENT //
+ );
+
+ String endpointName = getPath();
+ Console.error(getAPI().getName() + " error: endpoint " + endpointName
+ + " failed: '" + message + "'");
+ try
+ {
+ PrintWriter writer = response.getWriter();
+ writer.write("message=Endpoint " + endpointName + ": " + message);
+ } catch (IOException e)
+ {
+ Console.info("Exception writing to REST response", 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();
+ }
+
+ protected boolean checkParameters(HttpServletRequest request,
+ HttpServletResponse response, int i)
+ {
+ String[] parameters = getEndpointPathParameters(request);
+
+ // check we can run fetchsequence
+ if (parameters.length < i)
+ {
+ returnError(request, response,
+ "requires parameters:" + getParameters() + "\n" + getName()
+ + ": " + getDescription());
+ return false;
+ }
+ return true;
+ }
+
+ public int[][] parseIntRanges(String rangesString)
+ {
+ if (rangesString.equals("*"))
+ {
+ return new int[][] { { -1 }, { -1 } };
+ }
+ String[] rangeStrings = rangesString.split(",");
+ int[][] ranges = new int[2][rangeStrings.length];
+ for (int i = 0; i < rangeStrings.length; i++)
+ {
+ String range = rangeStrings[i];
+ try
+ {
+ int hyphenpos = range.indexOf('-');
+ if (hyphenpos < 0)
+ {
+ ranges[0][i] = Integer.parseInt(range);
+ ranges[1][i] = ranges[0][i];
+ }
+ else
+ {
+ ranges[0][i] = Integer.parseInt(range.substring(0, hyphenpos));
+ ranges[1][i] = Integer.parseInt(range.substring(hyphenpos + 1));
+ }
+ } catch (NumberFormatException nfe)
+ {
+ return null;
+ }
+ }
+ return ranges;
+ }
+
+ /*
+ * Get all AlignFrames or just one if requested to work on a specific window (fromId query string parameter)
+ */
+ protected AlignFrame[] getAlignFrames(HttpServletRequest request,
+ boolean all)
+ {
+ return getAlignFrames(request, "fromId", all);
+ }
+
+ protected AlignFrame[] getAlignFrames(HttpServletRequest request,
+ String idParam, boolean all)
+ {
+ String fromIdString = request.getParameter(idParam);
+
+ if (fromIdString != null)
+ {
+ AlignFrame af = AlignFrame.getAlignFrameFromRestId(fromIdString);
+ return af == null ? null : new AlignFrame[] { af };
+ }
+ else if (all)
+ {
+ return Desktop.getAlignFrames();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ protected AlignFrame getAlignFrameFromId(HttpServletRequest request)
+ {
+ return getAlignFrameFromId(request, "fromId");
+ }
+
+ protected AlignFrame getAlignFrameFromId(HttpServletRequest request,
+ String idParam)
+ {
+ AlignFrame[] afs = getAlignFrames(request, idParam, false);
+ return (afs == null || afs.length < 1 || afs[0] == null) ? null
+ : afs[0];
+ }
+
+ protected boolean deleteFromCache()
+ {
+ return false;
+ }
+
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+package jalview.rest;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.SequenceFetcher;
+import jalview.util.DBRefUtils;
+
+public class FetchSequencesEndpoint extends AbstractEndpointAsync
+{
+ public FetchSequencesEndpoint(API api)
+ {
+ super(api, path, name, parameters, description);
+ }
+
+ private static final String path = "fetchsequences";
+
+ private static final String name = "Fetch Sequences";
+
+ private static final String parameters = "<resource name>/<sequence ids>";
+
+ private static final String description = "Fetch sequences from online resource";
+
+ private SequenceFetcher sf;
+
+ @Override
+ protected void initialise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ if (!checkParameters(request, response, 2))
+ {
+ return;
+ }
+ String[] parameters = getEndpointPathParameters(request);
+
+ 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;
+ sf = new SequenceFetcher(desktop, db, dbId, false);
+ setCompletableFuture(sf.ok_actionPerformed(true, getId()));
+ }
+
+ protected void processAsync(HttpServletRequest request,
+ HttpServletResponse response, Map<String, Object> map)
+ {
+ // all the work being done by the SequenceFetcher!
+ Console.warn("THIS SHOULD NOT BE RUN");
+ }
+
+ @Override
+ protected void atEnd()
+ {
+ sf.close_actionPerformed(null);
+ }
+
+ @Override
+ protected String finishedResponseString(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ AlignFrame af = getAlignFrameFromId(request, "id");
+ if (af == null)
+ return null;
+ List<AlignmentViewPanel> aps = (List<AlignmentViewPanel>) af
+ .getAlignPanels();
+ StringBuilder sb = new StringBuilder();
+ for (AlignmentViewPanel ap : aps)
+ {
+ AlignmentI al = ap.getAlignment();
+ if (al == null)
+ continue;
+ List<SequenceI> seqs = (List<SequenceI>) al.getSequences();
+ for (SequenceI seq : seqs)
+ {
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append(seq.getName());
+ }
+ }
+ sb.insert(0, "sequences=");
+ sb.append("\n");
+ return sb.toString();
+ }
+
+}
--- /dev/null
+package jalview.rest;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+
+public class GetCrossReferencesEndpoint extends AbstractEndpointAsync
+{
+ public GetCrossReferencesEndpoint(API api)
+ {
+ super(api, path, name, parameters, description);
+ }
+
+ private static final String path = "getcrossreferences";
+
+ private static final String name = "Get Cross References";
+
+ private static final String parameters = "<resource name>/<sequence ids>?fromId=<id>";
+
+ private static final String description = "Get Cross References for alignment <id>";
+
+ private String resourceName;
+
+ private AlignFrame af;
+
+ private SequenceI[] seqs;
+
+ @Override
+ protected void initialise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ if (!checkParameters(request, response, 1))
+ {
+ return;
+ }
+ String[] parameters = getEndpointPathParameters(request);
+
+ resourceName = parameters[0];
+
+ String fromId = request.getParameter("fromId");
+ setId(request, resourceName + "::" + fromId);
+
+ if (checkStatus(request, response))
+ {
+ return;
+ }
+
+ af = getAlignFrameFromId(request);
+ if (af == null)
+ {
+ returnError(request, response, "Could not find results");
+ return;
+ }
+
+ if (af.canShowProducts())
+ {
+ List<String> products = af.getProducts();
+ if (products == null)
+ {
+ returnError(request, response,
+ "no cross reference products available");
+ return;
+ }
+ if (!products.contains(resourceName))
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("cross reference product '").append(resourceName)
+ .append("' not available: available products are\n");
+ sb.append("products=");
+ boolean first = true;
+ for (String p : products)
+ {
+ if (!first)
+ {
+ sb.append(",");
+ first = false;
+ }
+ sb.append(p);
+ }
+ sb.append("\n");
+ returnError(request, response, sb.toString());
+ return;
+ }
+ }
+
+ seqs = af.getViewport().getAlignment().getSequencesArray();
+ if (seqs == null || seqs.length == 0)
+ {
+ // nothing to do
+ returnError(request, response, "no sequences selected");
+ return;
+ }
+
+ final boolean dna = af.getViewport().getAlignment().isNucleotide();
+ setCompletableFuture(
+ af.showProductsFor(seqs, dna, resourceName, true, getId()));
+ }
+
+ protected void processAsync(HttpServletRequest request,
+ HttpServletResponse response, Map<String, Object> map)
+ {
+ // all the work being done by the AlignFrame!
+ Console.warn("THIS SHOULD NOT BE RUN");
+ }
+
+ protected String finishedResponseString(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ AlignFrame af = AlignFrame.getAlignFrameFromRestId(getId());
+ if (af == null)
+ {
+ return null;
+ }
+ List<AlignmentViewPanel> aps = (List<AlignmentViewPanel>) af
+ .getAlignPanels();
+ StringBuilder sb = new StringBuilder();
+ for (AlignmentViewPanel ap : aps)
+ {
+ AlignmentI al = ap.getAlignment();
+ List<SequenceI> seqs = (List<SequenceI>) al.getSequences();
+ for (SequenceI seq : seqs)
+ {
+ if (sb.length() > 0)
+ sb.append(",");
+ sb.append(seq.getName());
+ }
+ }
+ sb.insert(0, "sequences=");
+ sb.append("\n");
+ return sb.toString();
+ }
+
+}
--- /dev/null
+package jalview.rest;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.structure.StructureSelectionManager;
+
+public class HighlightSequenceEndpoint extends AbstractEndpoint
+{
+ public HighlightSequenceEndpoint(API api)
+ {
+ super(api, path, name, parameters, description);
+ }
+
+ protected static final String path = "highlightsequence";
+
+ private static final String name = "Highlight sequence position";
+
+ private static final String parameters = "<sequence names>/<position>";
+
+ private static final String description = "Highlight the specified sequences at the specified position";
+
+ public void processEndpoint(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ if (!checkParameters(request, response, 2))
+ {
+ return;
+ }
+ String[] parameters = getEndpointPathParameters(request);
+
+ String posString = parameters[1];
+ int pos = -1;
+ try
+ {
+ pos = Integer.parseInt(posString);
+ } catch (NumberFormatException e)
+ {
+ returnError(request, response,
+ "Could not parse postition integer " + posString);
+ }
+
+ String sequenceNames = parameters[0];
+
+ Map<SequenceI, StructureSelectionManager> ssmMap = new HashMap<>();
+ AlignFrame[] alignFrames = getAlignFrames(request, true);
+ if (alignFrames == null)
+ {
+ returnError(request, response, "could not find results");
+ return;
+ }
+ for (int i = 0; i < alignFrames.length; i++)
+ {
+ AlignFrame af = alignFrames[i];
+ List<AlignmentViewPanel> aps = (List<AlignmentViewPanel>) af
+ .getAlignPanels();
+ for (AlignmentViewPanel ap : aps)
+ {
+ StructureSelectionManager ssm = ap.getStructureSelectionManager();
+ AlignmentI al = ap.getAlignment();
+ List<SequenceI> seqs = (List<SequenceI>) al.getSequences();
+ for (SequenceI seq : seqs)
+ {
+ if (sequenceNames.equals(seq.getName()))
+ {
+ ssmMap.put(seq, ssm);
+ }
+ }
+ }
+ }
+ // highlight
+ for (SequenceI seq : ssmMap.keySet())
+ {
+ StructureSelectionManager ssm = ssmMap.get(seq);
+ if (ssm == null)
+ {
+ continue;
+ }
+ ssm.mouseOverSequence(seq, pos, -1, null);
+ }
+
+ }
+}
\ No newline at end of file
--- /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 java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.bind.DatatypeConverter;
+
+import jalview.bin.Console;
+import jalview.gui.CutAndPasteTransfer;
+import jalview.gui.Desktop;
+
+public class InputAlignmentEndpoint extends AbstractEndpointAsync
+{
+ public InputAlignmentEndpoint(API api)
+ {
+ super(api, path, name, parameters, description);
+ }
+
+ protected static final String path = "inputalignment";
+
+ private static final String name = "Input Alignment";
+
+ private static final String parameters = "POST <body> | GET ?[data=<data>|file=<fileURI>|url=<URL>]";
+
+ private static final String description = "Input an alignment from POST request body, GET request 'data' parameter, local file in 'file' parameter, url in 'url' parameter";
+
+ private String fileString;
+
+ private String urlString;
+
+ private String method;
+
+ private String data;
+
+ private String body;
+
+ private boolean isPost;
+
+ private boolean hasData;
+
+ private String access = null;
+
+ private String ref = null;
+
+ @Override
+ protected void initialise(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ fileString = request.getParameter("file");
+ urlString = request.getParameter("url");
+ method = request.getMethod().toLowerCase();
+ data = request.getParameter("data");
+ body = null;
+ isPost = method.equalsIgnoreCase("post");
+ hasData = data != null;
+
+ if (isPost)
+ {
+ access = "post";
+ try
+ {
+ body = getRequestBody(request);
+ } catch (IOException e)
+ {
+ returnError(request, response, "could not read POST body");
+ Console.debug("Could not read POST body", e);
+ return;
+ }
+ // for ref see md5 later
+ }
+ else if (hasData)
+ {
+ 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
+ String content;
+ if (isPost || hasData)
+ {
+ content = isPost ? body : data;
+ objectsPassedToProcessAsync.put("content", content); // needed as "final
+ // String" in process
+ try
+ {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(content.getBytes());
+ byte[] digest = md5.digest();
+ ref = DatatypeConverter.printBase64Binary(digest).toLowerCase();
+ } catch (NoSuchAlgorithmException e)
+ {
+ Console.debug("Could not find MD5 algorithm", e);
+ }
+ }
+ else
+ {
+ content = null;
+ }
+ objectsPassedToProcessAsync.put("content", content);
+
+ setId(request, access + "::" + ref);
+ }
+
+ protected void process(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ processAsync(request, response, null);
+ }
+
+ protected void processAsync(HttpServletRequest request,
+ HttpServletResponse response, final Map<String, Object> finalMap)
+ {
+ String content = (String) finalMap.get("content");
+ if (isPost || hasData)
+ {
+ // 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 + "'");
+ Console.debug("Could not resolve file '" + fileString + "'", 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;
+ }
+ }
+ }
+}
*/
package jalview.rest;
-import jalview.httpserver.AbstractRequestHandler;
-
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.BindException;
+import java.util.HashMap;
+import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+import jalview.bin.Console;
+import jalview.httpserver.AbstractRequestHandler;
+import jalview.httpserver.HttpServer;
+
/**
* A simple handler to process (or delegate) HTTP requests on /jalview/rest
*/
public class RestHandler extends AbstractRequestHandler
{
+
+ public enum Status
+ {
+ STARTED, IN_PROGRESS, FINISHED, ERROR, NOT_RUN
+ }
+
+ public interface EndpointI
+ {
+ public String getPath();
+
+ public String getName();
+
+ public String getParameters();
+
+ public String getDescription();
+
+ public void processEndpoint(HttpServletRequest request,
+ HttpServletResponse response);
+
+ }
+
private static final String MY_PATH = "rest";
private static final String MY_NAME = "Rest";
+ private String missingEndpointMessage = null;
+
+ private boolean init = false;
+
+ // map of method names and method handlers
+ private Map<String, EndpointI> endpoints = null;
+
+ protected Map<String, EndpointI> getEndpoints()
+ {
+ return endpoints;
+ }
+
/**
* Singleton instance of this class
*/
*
* @throws BindException
*/
- private RestHandler() throws BindException
+ protected RestHandler() throws BindException
{
- setPath(MY_PATH);
+ init();
/*
* We don't register the handler here - this is done as a special case in
* Currently just echoes the request; add helper classes as required to
* process requests
*/
- final String queryString = request.getQueryString();
- final String reply = "REST not yet implemented; received "
- + request.getMethod() + ": " + request.getRequestURL()
- + (queryString == null ? "" : "?" + queryString);
- System.out.println(reply);
+ System.out.println(request.toString());
+ if (endpoints == null)
+ {
+ final String queryString = request.getQueryString();
+ final String reply = "REST not yet implemented; received "
+ + request.getMethod() + ": " + request.getRequestURL()
+ + (queryString == null ? "" : "?" + queryString);
+ System.out.println(reply);
+
+ response.setHeader("Cache-Control", "no-cache/no-store");
+ response.setHeader("Content-type", "text/plain");
+ final PrintWriter writer = response.getWriter();
+ writer.write(reply);
+ return;
+ }
+
+ String endpointName = getRequestedEndpointName(request);
+
+ if (!endpoints.containsKey(endpointName)
+ || endpoints.get(endpointName) == null)
+ {
+
+ response.setHeader("Cache-Control", "no-cache/no-store");
+ response.setHeader("Content-type", "text/plain");
+ PrintWriter writer = response.getWriter();
+ writer.write(missingEndpointMessage == null
+ ? "REST endpoint '" + endpointName + "' not defined"
+ : missingEndpointMessage);
+ writer.write("\n");
+ writer.write("Available endpoints are:\n");
+ ContextHandler ch = HttpServer.getInstance().getContextHandler(this);
+ String base = HttpServer.getInstance().getUri().toString();
+ String contextPath = ch == null ? "" : ch.getContextPath();
+ for (String key : endpoints.keySet())
+ {
+ writer.write(base + contextPath + "/" + key + "\n");
+ }
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
response.setHeader("Cache-Control", "no-cache/no-store");
response.setHeader("Content-type", "text/plain");
- final PrintWriter writer = response.getWriter();
- writer.write(reply);
- writer.close();
+ EndpointI ep = endpoints.get(endpointName);
+ ep.processEndpoint(request, response);
+
+ return;
}
/**
return MY_NAME;
}
-}
+ /**
+ * Initialise methods
+ *
+ * @throws BindException
+ */
+ protected void init() throws BindException
+ {
+ init(MY_PATH);
+ }
+
+ protected void init(String path) throws BindException
+ {
+ setPath(path);
+ // Override this in extended class
+ // e.g. registerHandler and addEndpoints
+ }
+
+ protected boolean addEndpoint(EndpointI ep)
+ {
+ if (endpoints == null)
+ {
+ endpoints = new HashMap<>();
+ }
+ AbstractEndpoint e = (AbstractEndpoint) ep;
+ endpoints.put(ep.getPath(), ep);
+ return true;
+ }
+
+ protected String getRequestedEndpointName(HttpServletRequest request)
+ {
+ String pathInfo = request.getPathInfo();
+ int slashpos = pathInfo.indexOf('/', 1);
+ return slashpos > 1 ? pathInfo.substring(1, slashpos)
+ : pathInfo.substring(1);
+ }
+
+ 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(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ String endpointName = getRequestedEndpointName(request);
+ Console.error(getName() + " error: endpoint " + endpointName
+ + " failed: '" + message + "'");
+ try
+ {
+ PrintWriter writer = response.getWriter();
+ writer.write("Endpoint " + endpointName + ": " + message);
+ } catch (IOException e)
+ {
+ Console.debug("Could not write to REST response for endpoint "
+ + endpointName, e);
+ }
+ }
+
+ protected void returnStatus(HttpServletResponse response, String id,
+ Status status)
+ {
+ try
+ {
+ PrintWriter writer = response.getWriter();
+ if (id != null)
+ writer.write("id=" + id + "\n");
+ if (status != null)
+ writer.write("status=" + status.toString() + "\n");
+ } catch (IOException e)
+ {
+ Console.debug("Could not write status to REST response for id:" + id,
+ 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.util.Arrays;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+
+public class SelectSequencesEndpoint extends AbstractEndpoint
+{
+ public SelectSequencesEndpoint(API api)
+ {
+ super(api, path, name, parameters, description);
+ }
+
+ protected static final String path = "selectsequences";
+
+ private static final String name = "Select sequence(s) positions";
+
+ private static final String parameters = "<sequence names>/<range>";
+
+ private static final String description = "Select the specified sequence(s) with the specified range";
+
+ public void processEndpoint(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ if (!checkParameters(request, response, 2))
+ {
+ return;
+ }
+ String[] parameters = getEndpointPathParameters(request);
+
+ String rangesString = parameters[1];
+ int[][] ranges = parseIntRanges(rangesString);
+ if (ranges == null || ranges.length < 2 || ranges[0].length < 1)
+ {
+ returnError(request, response,
+ "couldn't parse range '" + rangesString + "'");
+ return;
+ }
+ if (ranges[0].length > 1)
+ {
+ returnError(request, response,
+ "only provide 1 range '" + rangesString + "'");
+ return;
+ }
+ int start = ranges[0][0];
+ int end = ranges[1][0];
+
+ String sequenceNamesString = parameters[0];
+ List<String> sequenceNames = Arrays
+ .asList(sequenceNamesString.split(","));
+
+ AlignFrame[] alignFrames = getAlignFrames(request, true);
+ if (alignFrames == null)
+ {
+ returnError(request, response, "could not find results");
+ return;
+ }
+ for (int i = 0; i < alignFrames.length; i++)
+ {
+ AlignFrame af = alignFrames[i];
+ List<AlignmentViewPanel> aps = (List<AlignmentViewPanel>) af
+ .getAlignPanels();
+ for (AlignmentViewPanel ap : aps)
+ {
+ AlignViewportI avp = ap.getAlignViewport();
+ AlignmentI al = ap.getAlignment();
+ List<SequenceI> seqs = (List<SequenceI>) al.getSequences();
+ SequenceGroup stretchGroup = new SequenceGroup();
+ for (SequenceI seq : seqs)
+ {
+ if (sequenceNames.contains(seq.getName())
+ || (sequenceNamesString.equals("*")
+ && alignFrames.length == 1))
+ {
+ stretchGroup.addSequence(seq, false);
+ }
+ }
+ if (start == -1 && end == -1) // "*" as range
+ {
+ stretchGroup.setStartRes(al.getStartRes());
+ stretchGroup.setEndRes(al.getEndRes());
+ }
+ else
+ {
+ stretchGroup.setStartRes(start);
+ stretchGroup.setEndRes(end);
+ }
+ avp.setSelectionGroup(stretchGroup);
+ ap.paintAlignment(false, false);
+ avp.sendSelection();
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env bash
+
+java -cp "bin/main:j11lib/*:resources" jalview.bin.Launcher ${@}
--- /dev/null
+wget -q -S -O - 'http://localhost:2021/jalview/api/fetchsequences/ensembl/ENSG00000139618?id=myBRCA2'
+wget -q -S -O - 'http://localhost:2021/jalview/api/getcrossreferences/UNIPROT?fromId=myBRCA2'
+wget -q -S -O - "http://localhost:2021/jalview/api/highlightsequence/ENST00000544455/30000?fromId=myBRCA2"
+N=22946; while [ $N -lt 23002 ]; do wget -q -S -O - "http://localhost:2021/jalview/api/highlightsequence/ENST00000544455/${N}?fromId=myBRCA2"; N=$((N+1)); sleep 0.2; done
+wget -q -S -O - 'http://localhost:2021/jalview/api/selectsequences/*/*?fromId=myBRCA2'
+wget -q -S -O - 'http://localhost:2021/jalview/api/selectsequences/*/22946-23001?fromId=myBRCA2'
+wget -q -S -O - 'http://localhost:2021/jalview/api/selectsequences/ENSG00000139618,ENST00000544455,ENST00000530893,ENST00000380152,ENST00000680887,ENST00000614259,ENST00000665585,ENST00000528762/22946-23001?fromId=myBRCA2'
+wget -q -S -O - 'http://localhost:2021/jalview/api/selectsequences/ENSG00000139618,ENST00000544455,ENST00000380152,ENST00000680887,ENST00000614259,ENST00000528762/22946-23001?fromId=myBRCA2'
+### open 3D structure 6hqu and repeat highlight
+