package ext.edu.ucsf.rbvi.strucviz2;
-import jalview.ws.HttpClientUtils;
-
import java.awt.Color;
import java.io.BufferedReader;
import java.io.File;
import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads;
+import jalview.ws.HttpClientUtils;
+
/**
* This object maintains the Chimera communication information.
*/
ModelType type)
{
logger.info("chimera open " + modelPath);
- stopListening();
+ // stopListening();
List<String> response = null;
// TODO: [Optional] Handle modbase models
if (type == ModelType.MODBASE_MODEL)
}
sendChimeraCommand("focus", false);
- startListening();
+ // startListening(); // see ChimeraListener
return models;
}
public void startListening()
{
- sendChimeraCommand("listen start models; listen start select", false);
+ sendChimeraCommand("listen start models; listen start selection", false);
}
public void stopListening()
{
- sendChimeraCommand("listen stop models; listen stop select", false);
+ sendChimeraCommand("listen stop models; listen stop selection", false);
+ }
+
+ /**
+ * Tell Chimera we are listening on the given URI
+ *
+ * @param uri
+ */
+ public void startListening(String uri)
+ {
+ sendChimeraCommand("listen start models url " + uri, false);
+ sendChimeraCommand("listen start select prefix SelectionChanged url "
+ + uri, false);
+ }
+
+ /**
+ * Tell Chimera we have stopped listening on the given URI
+ *
+ * @param uri
+ */
+ public void stopListening(String uri)
+ {
+ sendChimeraCommand("listen stop models url " + uri, false);
+ sendChimeraCommand("listen stop selection url " + uri, false);
}
/**
*/
public void select(String command)
{
- sendChimeraCommand("listen stop select; " + command
- + "; listen start select", false);
+ sendChimeraCommand("listen stop selection; " + command
+ + "; listen start selection", false);
}
public void focus()
return selectedModelsMap;
}
+ /**
+ * Sends a 'list selection level residue' command to Chimera and returns the
+ * list of selected atomspecs
+ *
+ * @return
+ */
public List<String> getSelectedResidueSpecs()
{
List<String> selectedResidues = new ArrayList<String>();
structureManager.setChimeraPathProperty(workingPath);
// TODO: [Optional] Check Chimera version and show a warning if below 1.8
// Ask Chimera to give us updates
- startListening();
+ // startListening(); // later - see ChimeraListener
return true;
}
*/
protected List<String> sendRestCommand(String command)
{
+ // System.out.println("Rest: " + command);
// TODO start a separate thread to do this so we don't block?
String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
List<NameValuePair> commands = new ArrayList<NameValuePair>(1);
{
return busy;
}
-
}
--- /dev/null
+package jalview.ext.rbvi.chimera;
+
+import java.net.BindException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import jalview.httpserver.AbstractRequestHandler;
+import jalview.httpserver.HttpServer;
+import jalview.structure.SelectionSource;
+
+/**
+ * This is a simple Http handler that can listen for selections in Chimera.
+ * <p/>
+ * Lifecycle:
+ * <ul>
+ * <li>Start the Chimera process</li>
+ * <li>Start the REST service on Chimera, get the port number it is listening on
+ * </li>
+ * <li>Start the ChimeraListener, get the port number it is listening on</li>
+ * <li>Send a 'listen' command to Chimera with the URL of the listener</li>
+ * </ul>
+ *
+ * @author gmcarstairs
+ *
+ */
+public class ChimeraListener extends AbstractRequestHandler implements
+ SelectionSource
+{
+ /*
+ * A static counter so each listener can be associated with a distinct context
+ * root (/chimera0,1,2,3...). This is needed so we can fetch selections from
+ * multiple Chimera instances without confusion.
+ */
+ private static int chimeraId = 0;
+
+ /*
+ * Path below context root that identifies this handler
+ */
+ private static final String LISTENER_PATH = "chimera";
+
+ /*
+ * Value of chimeraId (0, 1, 2...) for this instance
+ */
+ private int myChimeraId = 0;
+
+ /*
+ * A reference to the object by which we can talk to Chimera
+ */
+ private JalviewChimeraBinding chimeraBinding;
+
+ /*
+ * The URI of this listener
+ */
+ private String uri;
+
+ /**
+ * Constructor that also registers this as an Http request handler on path
+ * /chimeraN, where N is incremented for each instance. Call getUri to get the
+ * resulting URI for this handler.
+ *
+ * @param chimeraBinding
+ * @throws BindException
+ * if no free port can be assigned
+ */
+ public ChimeraListener(JalviewChimeraBinding binding)
+ throws BindException
+ {
+ myChimeraId = chimeraId++;
+ this.chimeraBinding = binding;
+ final String path = LISTENER_PATH + myChimeraId;
+ this.uri = HttpServer.getInstance().registerHandler(path, this);
+ }
+
+ /**
+ * Returns the URI on which we are listening
+ *
+ * @return
+ */
+ public String getUri()
+ {
+ return this.uri;
+ }
+
+ /**
+ * Process a message from Chimera
+ */
+ @Override
+ protected void processRequest(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ // dumpRequest(request);
+ String message = request.getParameter("chimeraNotification");
+ if ("selection changed".equals(message))
+ {
+ this.chimeraBinding.highlightChimeraSelection();
+ }
+ else
+ {
+ System.err.println("Unexpected chimeraNotification: " + message);
+ // do it anyway for now!
+ this.chimeraBinding.highlightChimeraSelection();
+ }
+ }
+
+ /**
+ * Deregister this listener and close it down
+ *
+ * @throws Exception
+ */
+ public void shutdown()
+ {
+ try
+ {
+ HttpServer.getInstance().removeHandler(this);
+ stop();
+ } catch (Exception e)
+ {
+ System.err.println("Error stopping chimera listener: "
+ + e.getMessage());
+ }
+ }
+}
*/
package jalview.ext.rbvi.chimera;
+import java.awt.Color;
+import java.net.BindException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
+
import jalview.api.AlignmentViewPanel;
import jalview.api.FeatureRenderer;
import jalview.api.SequenceRenderer;
import jalview.datamodel.SequenceI;
import jalview.schemes.ColourSchemeI;
import jalview.schemes.ResidueProperties;
+import jalview.structure.AtomSpec;
import jalview.structure.StructureMapping;
import jalview.structure.StructureMappingcommandSet;
import jalview.structure.StructureSelectionManager;
import jalview.util.Comparison;
import jalview.util.MessageManager;
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
-import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
-
public abstract class JalviewChimeraBinding extends AAStructureBindingModel
{
private StructureManager csm;
+ /*
+ * Object through which we talk to Chimera
+ */
private ChimeraManager viewer;
/*
+ * Object which listens to Chimera notifications
+ */
+ private ChimeraListener chimeraListener;
+
+ /*
* set if chimera state is being restored from some source - instructs binding
* not to apply default display style when structure set is updated for first
* time.
private String lastCommand;
- private String lastMessage;
-
private boolean loadedInline;
/**
private List<String> lastReply;
+ /*
+ * incremented every time a load notification is successfully handled -
+ * lightweight mechanism for other threads to detect when they can start
+ * referring to new structures.
+ */
+ private long loadNotifiesHandled = 0;
+
/**
* Open a PDB structure file in Chimera and set up mappings from Jalview.
*
/*
* If Chimera doesn't yet have this model, ask it to open it, and retrieve
- * the model names added by Chimera.
+ * the model name(s) added by Chimera.
*/
if (!alreadyOpen)
{
viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
- modelsToMap = viewer.getModelList();
- modelsToMap.removeAll(oldList);
+ List<ChimeraModel> newList = viewer.getModelList();
+ // JAL-1728 newList.removeAll(oldList) does not work
+ for (ChimeraModel cm : newList)
+ {
+ if (cm.getModelName().equals(pe.getId()))
+ {
+ modelsToMap.add(cm);
+ }
+ }
}
chimeraMaps.put(file, modelsToMap);
}
/**
+ * Start a dedicated HttpServer to listen for Chimera notifications, and tell
+ * it to start listening
+ */
+ public void startChimeraListener()
+ {
+ try
+ {
+ chimeraListener = new ChimeraListener(this);
+ viewer.startListening(chimeraListener.getUri());
+ } catch (BindException e)
+ {
+ System.err.println("Failed to start Chimera listener: "
+ + e.getMessage());
+ }
+ }
+
+ /**
* Constructor
*
* @param ssm
}
/**
- * Close down the Jalview viewer, and (optionally) the associated Chimera
- * window.
+ * Close down the Jalview viewer and listener, and (optionally) the associated
+ * Chimera window.
*/
public void closeViewer(boolean closeChimera)
{
{
viewer.exitChimera();
}
+ if (this.chimeraListener != null)
+ {
+ chimeraListener.shutdown();
+ chimeraListener = null;
+ }
lastCommand = null;
viewer = null;
+
releaseUIResources();
}
*
* <pre>
* Done by generating a command like (to 'highlight' position 44)
- * ~select #0:43.C;select #0:44.C
- * Note this removes the selection from the previous position.
+ * ~show #0:43.C;show #0:44.C
+ * Note this removes the highlight from the previous position.
* </pre>
*/
public void highlightAtom(int atomIndex, int pdbResNum, String chain,
String cmd = command.toString();
if (cmd.length() > 0)
{
+ viewer.stopListening(chimeraListener.getUri());
viewer.sendChimeraCommand(cmd, false);
+ viewer.startListening(chimeraListener.getUri());
}
viewerCommandHistory(true);
this.lastMousedOverAtomSpec = atomSpec;
}
}
+ /**
+ * Query Chimera for its current selection, and highlight it on the alignment
+ */
+ public void highlightChimeraSelection()
+ {
+ List<String> selection = viewer.getSelectedResidueSpecs();
+ // System.out.println("Chimera selection: " + selection.toString());
+
+ // TODO handle more than one!!
+ if (selection.size() > 0)
+ {
+ highlightChimeraSelection(selection);
+ }
+ }
+
private void log(String message)
{
System.err.println("## Chimera log: " + message);
// + ((debug || enable) ? "on" : "off"));
}
- public void loadInline(String string)
- {
- loadedInline = true;
- // TODO: re JAL-623
- // viewer.loadInline(strModel, isAppend);
- // could do this:
- // construct fake fullPathName and fileName so we can identify the file
- // later.
- // Then, construct pass a reader for the string to Jmol.
- // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
- // fileName, null, reader, false, null, null, 0);
- // viewer.openStringInline(string);
- log("cannot load inline in Chimera, yet");
- }
-
- public void mouseOverStructure(int atomIndex, String strInfo)
+ /**
+ * Propagate atom selections from Chimera
+ *
+ * @param atoms
+ * for example "#0:70.A" (model/residue/chain)
+ */
+ public void highlightChimeraSelection(List<String> atoms)
{
- // function to parse a mouseOver event from Chimera
- //
- int pdbResNum;
- int alocsep = strInfo.indexOf("^");
- int mdlSep = strInfo.indexOf("/");
- int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
-
- if (chainSeparator == -1)
+ List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
+ for (String atomSpec : atoms)
{
- chainSeparator = strInfo.indexOf(".");
- if (mdlSep > -1 && mdlSep < chainSeparator)
+ int hashPos = atomSpec.indexOf("#");
+ int colonPos = atomSpec.indexOf(":");
+ int dotPos = atomSpec.indexOf(".");
+
+ if (colonPos == -1)
{
- chainSeparator1 = chainSeparator;
- chainSeparator = mdlSep;
+ continue; // malformed
}
- }
- // handle insertion codes
- if (alocsep != -1)
- {
- pdbResNum = Integer.parseInt(strInfo.substring(
- strInfo.indexOf("]") + 1, alocsep));
-
- }
- else
- {
- pdbResNum = Integer.parseInt(strInfo.substring(
- strInfo.indexOf("]") + 1, chainSeparator));
- }
- String chainId;
+ int pdbResNum = Integer.parseInt(dotPos == -1 ? atomSpec
+ .substring(colonPos) : atomSpec.substring(colonPos + 1,
+ dotPos));
- if (strInfo.indexOf(":") > -1)
- {
- chainId = strInfo.substring(strInfo.indexOf(":") + 1,
- strInfo.indexOf("."));
- }
- else
- {
- chainId = " ";
- }
-
- String pdbfilename = modelFileNames[frameNo]; // default is first or current
- // model
- if (mdlSep > -1)
- {
- if (chainSeparator1 == -1)
- {
- chainSeparator1 = strInfo.indexOf(".", mdlSep);
+ String chainId = dotPos == -1 ? "" : atomSpec.substring(dotPos + 1);
+ int modelId = 0;
+ try {
+ modelId = Integer.valueOf(atomSpec.substring(hashPos + 1, colonPos));
+ } catch (NumberFormatException e) {
+ // ignore, default to model 0
}
- String mdlId = (chainSeparator1 > -1) ? strInfo.substring(mdlSep + 1,
- chainSeparator1) : strInfo.substring(mdlSep + 1);
- try
+
+ /*
+ * Work out the pdbfilename from the model number
+ */
+ String pdbfilename = modelFileNames[frameNo];
+ findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
{
- // recover PDB filename for the model hovered over.
- int _mp = _modelFileNameMap.length - 1, mnumber = new Integer(mdlId)
- .intValue() - 1;
- while (mnumber < _modelFileNameMap[_mp])
+ for (ChimeraModel cm : chimeraMaps.get(pdbfile))
{
- _mp--;
- }
- pdbfilename = modelFileNames[_mp];
- if (pdbfilename == null)
- {
- // pdbfilename = new File(viewer.getModelFileName(mnumber))
- // .getAbsolutePath();
+ if (cm.getModelNumber() == modelId)
+ {
+ pdbfilename = pdbfile;
+ break findfileloop;
+ }
}
-
- } catch (Exception e)
- {
}
- ;
+ atomSpecs.add(new AtomSpec(pdbfilename, chainId, pdbResNum, 0));
}
- if (lastMessage == null || !lastMessage.equals(strInfo))
+ if (!atomSpecs.isEmpty())
{
- getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
+ getSsm().mouseOverStructure(atomSpecs);
}
-
- lastMessage = strInfo;
}
- public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
- {
- /**
- * this implements the toggle label behaviour copied from the original
- * structure viewer, MCView
- */
- if (strData != null)
- {
- System.err.println("Ignoring additional pick data string " + strData);
- }
- // rewrite these selections for chimera (DNA, RNA and protein)
- int chainSeparator = strInfo.indexOf(":");
- int p = 0;
- if (chainSeparator == -1)
- {
- chainSeparator = strInfo.indexOf(".");
- }
-
- String picked = strInfo.substring(strInfo.indexOf("]") + 1,
- chainSeparator);
- String mdlString = "";
- if ((p = strInfo.indexOf(":")) > -1)
- {
- picked += strInfo.substring(p + 1, strInfo.indexOf("."));
- }
-
- if ((p = strInfo.indexOf("/")) > -1)
- {
- mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
- }
- picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
- + mdlString + "))";
- viewerCommandHistory(false);
-
- if (!atomsPicked.contains(picked))
- {
- viewer.select(picked);
- atomsPicked.add(picked);
- }
- else
- {
- viewer.select("not " + picked);
- atomsPicked.remove(picked);
- }
- viewerCommandHistory(true);
- // TODO: in application this happens
- //
- // if (scriptWindow != null)
- // {
- // scriptWindow.sendConsoleMessage(strInfo);
- // scriptWindow.sendConsoleMessage("\n");
- // }
-
- }
-
- // incremented every time a load notification is successfully handled -
- // lightweight mechanism for other threads to detect when they can start
- // referring to new structures.
- private long loadNotifiesHandled = 0;
-
public long getLoadNotifiesHandled()
{
return loadNotifiesHandled;
}
- public void notifyFileLoaded(String fullPathName, String fileName2,
- String modelName, String errorMsg, int modelParts)
- {
- if (errorMsg != null)
- {
- fileLoadingError = errorMsg;
- refreshGUI();
- return;
- }
- // TODO: deal sensibly with models loaded inLine:
- // modelName will be null, as will fullPathName.
-
- // the rest of this routine ignores the arguments, and simply interrogates
- // the Jmol view to find out what structures it contains, and adds them to
- // the structure selection manager.
- fileLoadingError = null;
- String[] oldmodels = modelFileNames;
- modelFileNames = null;
- chainNames = new ArrayList<String>();
- chainFile = new HashMap<String, String>();
- boolean notifyLoaded = false;
- String[] modelfilenames = getPdbFile();
- // first check if we've lost any structures
- if (oldmodels != null && oldmodels.length > 0)
- {
- int oldm = 0;
- for (int i = 0; i < oldmodels.length; i++)
- {
- for (int n = 0; n < modelfilenames.length; n++)
- {
- if (modelfilenames[n] == oldmodels[i])
- {
- oldmodels[i] = null;
- break;
- }
- }
- if (oldmodels[i] != null)
- {
- oldm++;
- }
- }
- if (oldm > 0)
- {
- String[] oldmfn = new String[oldm];
- oldm = 0;
- for (int i = 0; i < oldmodels.length; i++)
- {
- if (oldmodels[i] != null)
- {
- oldmfn[oldm++] = oldmodels[i];
- }
- }
- // deregister the Jmol instance for these structures - we'll add
- // ourselves again at the end for the current structure set.
- getSsm().removeStructureViewerListener(this, oldmfn);
- }
- }
-
- // register ourselves as a listener and notify the gui that it needs to
- // update itself.
- getSsm().addStructureViewerListener(this);
-
- if (notifyLoaded)
- {
- FeatureRenderer fr = getFeatureRenderer(null);
- if (fr != null)
- {
- fr.featuresAdded();
- }
- refreshGUI();
- loadNotifiesHandled++;
- }
- setLoadingFromArchive(false);
- }
-
public void setJalviewColourScheme(ColourSchemeI cs)
{
colourBySequence = false;
}
}
jmb.setFinishedInit(true);
+
+ jmb.startChimeraListener();
}
void setChainMenuItems(List<String> chainNames)
if (pdbseq != null && pdbseq.getHeight() > 0)
{
// just use the file name from the first sequence's first PDBEntry
- filePath = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
- .elementAt(0)).getFile()).getAbsolutePath();
+ filePath = new File(pdbseq.getSequenceAt(0).getPDBId()
+ .elementAt(0).getFile()).getAbsolutePath();
processingEntry.setFile(filePath);
}
return filePath;
--- /dev/null
+package jalview.httpserver;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+/**
+ *
+ * @author gmcarstairs
+ *
+ */
+public abstract class AbstractRequestHandler extends AbstractHandler
+{
+ /**
+ * Handle an incoming Http request.
+ */
+ @Override
+ public void handle(String target, Request baseRequest,
+ HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ try
+ {
+ // dumpRequest(request); // debug
+ processRequest(request, response);
+ } catch (Throwable t)
+ {
+ /*
+ * Set server error status on response
+ */
+ System.err.println("Exception handling request "
+ + request.getRequestURI() + " : " + t.getMessage());
+ if (response.isCommitted())
+ {
+ /*
+ * Can't write an HTTP header once any response content has been written
+ */
+ System.err
+ .println("Unable to return HTTP 500 as response already committed");
+ }
+ else
+ {
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ } finally
+ {
+ response.getWriter().flush();
+ baseRequest.setHandled(true);
+ }
+ }
+
+ /**
+ * Subclasses should override this method to perform request processing
+ *
+ * @param request
+ * @param response
+ */
+ protected abstract void processRequest(HttpServletRequest request,
+ HttpServletResponse response);
+
+ /**
+ * For debug - writes HTTP request details to stdout
+ *
+ * @param request
+ */
+ protected void dumpRequest(HttpServletRequest request)
+ {
+ System.out.println(request.getMethod());
+ for (String hdr : Collections.list(request.getHeaderNames()))
+ {
+ for (String val : Collections.list(request.getHeaders(hdr)))
+ {
+ System.out.println(hdr + ": " + val);
+ }
+ }
+ for (String param : Collections.list(request.getParameterNames()))
+ {
+ for (String val : request.getParameterValues(param))
+ {
+ System.out.println(param + "=" + val);
+ }
+ }
+ }
+}
--- /dev/null
+package jalview.httpserver;
+
+import java.net.BindException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/**
+ * An HttpServer built on Jetty
+ *
+ * @author gmcarstairs
+ * @see http://eclipse.org/jetty/documentation/current/embedding-jetty.html
+ */
+public class HttpServer
+{
+ private static final String JALVIEW_PATH = "/jalview";
+
+ /*
+ * Singleton instance of this server
+ */
+ private static HttpServer instance;
+
+ /*
+ * The Http server
+ */
+ private Server server;
+
+ /*
+ * Registered handlers for context paths
+ */
+ private HandlerCollection contextHandlers;
+
+ /*
+ * Lookup of ContextHandler by its wrapped handler
+ */
+ Map<Handler, ContextHandler> myHandlers = new HashMap<Handler, ContextHandler>();
+
+ /*
+ * The context root for the server
+ */
+ private URI contextRoot;
+
+ /**
+ * Returns the singleton instance of this class.
+ *
+ * @return
+ * @throws BindException
+ */
+ public static HttpServer getInstance() throws BindException
+ {
+ synchronized (HttpServer.class)
+ {
+ if (instance == null) {
+ instance = new HttpServer();
+ }
+ return instance;
+ }
+ }
+
+ /**
+ * Private constructor to enforce use of singleton
+ *
+ * @throws BindException
+ * if no free port can be assigned
+ */
+ private HttpServer() throws BindException
+ {
+ startServer();
+ }
+
+ /**
+ * Start the http server with a default thread pool size of 1
+ *
+ * @throws BindException
+ */
+ private void startServer() throws BindException
+ {
+ try
+ {
+ // problem: how to both limit thread pool size and
+ // pick a random free port - two alternative constructors
+ QueuedThreadPool tp = new QueuedThreadPool(1, 1);
+ // server = new Server(tp); // ? fails with URI null
+ server = new Server(0); // pick a free port number
+
+ /*
+ * HttpServer shuts down with Jalview process
+ */
+ server.setStopAtShutdown(true);
+
+ /*
+ * Create a mutable set of handlers (can add handlers while the server is
+ * running)
+ */
+ // TODO how to combine this with context root "/jalview"
+ contextHandlers = new HandlerCollection(true);
+ server.setHandler(contextHandlers);
+
+ server.start();
+ contextRoot = server.getURI();
+ System.out.println("Jalview endpoint " + contextRoot);
+ } catch (Exception e)
+ {
+ System.err.println("Error trying to start HttpServer: "
+ + e.getMessage());
+ try
+ {
+ server.stop();
+ } catch (Exception e1)
+ {
+ e1.printStackTrace();
+ }
+ }
+ if (server == null)
+ {
+ throw new BindException("HttpServer failed to allocate a port");
+ }
+ }
+
+ /**
+ * Returns the URI on which we are listening
+ *
+ * @return
+ */
+ public URI getUri()
+ {
+ return server == null ? null : server.getURI();
+ }
+
+ /**
+ * For debug - write HTTP request details to stdout
+ *
+ * @param request
+ * @param response
+ */
+ protected void dumpRequest(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ for (String hdr : Collections.list(request.getHeaderNames()))
+ {
+ for (String val : Collections.list(request.getHeaders(hdr)))
+ {
+ System.out.println(hdr + ": " + val);
+ }
+ }
+ for (String param : Collections.list(request.getParameterNames()))
+ {
+ for (String val : request.getParameterValues(param))
+ {
+ System.out.println(param + "=" + val);
+ }
+ }
+ }
+
+ /**
+ * Stop the Http server.
+ */
+ public void stopServer()
+ {
+ if (server != null)
+ {
+ if (server.isStarted())
+ {
+ try
+ {
+ server.stop();
+ } catch (Exception e)
+ {
+ System.err.println("Error stopping Http Server on "
+ + server.getURI() + ": " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a handler for the given path and returns its URI
+ *
+ * @param path
+ * a path below the context root (without leading or trailing
+ * separator)
+ * @param handler
+ * @return
+ */
+ public String registerHandler(String path, AbstractRequestHandler handler)
+ {
+ // http://stackoverflow.com/questions/20043097/jetty-9-embedded-adding-handlers-during-runtime
+ ContextHandler ch = new ContextHandler();
+ ch.setContextPath("/" + path);
+ ch.setResourceBase(".");
+ ch.setClassLoader(Thread.currentThread()
+ .getContextClassLoader());
+ ch.setHandler(handler);
+
+ /*
+ * Remember the association so we can remove it later
+ */
+ this.myHandlers.put(handler, ch);
+
+ /*
+ * A handler added to a running server must be started explicitly
+ */
+ contextHandlers.addHandler(ch);
+ try
+ {
+ ch.start();
+ } catch (Exception e)
+ {
+ System.err.println("Error starting handler for " + path + ": "
+ + e.getMessage());
+ }
+
+ return this.contextRoot + ch.getContextPath().substring(1);
+ }
+
+ /**
+ * Removes the handler from the server; more precisely, remove the
+ * ContextHandler wrapping the specified handler
+ *
+ * @param handler
+ */
+ public void removeHandler(Handler handler)
+ {
+ /*
+ * Have to use this cached lookup table since there is no method
+ * ContextHandler.getHandler()
+ */
+ ContextHandler ch = myHandlers.get(handler);
+ if (ch != null)
+ {
+ contextHandlers.removeHandler(ch);
+ myHandlers.remove(handler);
+ }
+ }
+}
package jalview.structure;
/**
- * Java bean representing an atom in a PDB (or similar) structure model.
+ * Java bean representing an atom in a PDB (or similar) structure model or
+ * viewer
*
* @author gmcarstairs
*
*/
package jalview.structure;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import MCview.Atom;
+import MCview.PDBChain;
+
import jalview.analysis.AlignSeq;
import jalview.api.StructureSelectionManagerProvider;
import jalview.commands.CommandI;
import jalview.util.MappingUtils;
import jalview.util.MessageManager;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-
-import MCview.Atom;
-import MCview.PDBChain;
-
public class StructureSelectionManager
{
static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
}
}
+ /**
+ * Propagate mouseover of a single position in a structure
+ *
+ * @param pdbResNum
+ * @param chain
+ * @param pdbfile
+ */
public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
{
+ AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
+ List<AtomSpec> atoms = Collections.singletonList(atomSpec);
+ mouseOverStructure(atoms);
+ }
+
+ /**
+ * Propagate mouseover or selection of multiple positions in a structure
+ *
+ * @param atoms
+ */
+ public void mouseOverStructure(List<AtomSpec> atoms)
+ {
if (listeners == null)
{
// old or prematurely sent event
return;
}
- SearchResults results = null;
- SequenceI lastseq = null;
- int lastipos = -1, indexpos;
+ boolean hasSequenceListener = false;
for (int i = 0; i < listeners.size(); i++)
{
if (listeners.elementAt(i) instanceof SequenceListener)
{
- if (results == null)
- {
- results = new SearchResults();
- }
- for (StructureMapping sm : mappings)
+ hasSequenceListener = true;
+ }
+ }
+ if (!hasSequenceListener)
+ {
+ return;
+ }
+
+ SearchResults results = new SearchResults();
+ for (AtomSpec atom : atoms)
+ {
+ SequenceI lastseq = null;
+ int lastipos = -1;
+ for (StructureMapping sm : mappings)
+ {
+ if (sm.pdbfile.equals(atom.getPdbFile())
+ && sm.pdbchain.equals(atom.getChain()))
{
- if (sm.pdbfile.equals(pdbfile) && sm.pdbchain.equals(chain))
+ int indexpos = sm.getSeqPos(atom.getPdbResNum());
+ if (lastipos != indexpos && lastseq != sm.sequence)
{
- indexpos = sm.getSeqPos(pdbResNum);
- if (lastipos != indexpos && lastseq != sm.sequence)
+ results.addResult(sm.sequence, indexpos, indexpos);
+ lastipos = indexpos;
+ lastseq = sm.sequence;
+ // construct highlighted sequence list
+ for (AlignedCodonFrame acf : seqmappings)
{
- results.addResult(sm.sequence, indexpos, indexpos);
- lastipos = indexpos;
- lastseq = sm.sequence;
- // construct highlighted sequence list
- for (AlignedCodonFrame acf : seqmappings)
- {
- acf.markMappedRegion(sm.sequence, indexpos, results);
- }
+ acf.markMappedRegion(sm.sequence, indexpos, results);
}
}
}
}
}
- if (results != null)
+ if (!results.isEmpty())
{
- for (int i = 0; i < listeners.size(); i++)
+ for (Object li : listeners)
{
- Object li = listeners.elementAt(i);
if (li instanceof SequenceListener)
{
((SequenceListener) li).highlightSequence(results);
Set<AlignedCodonFrame> codonFrames = protein.getAlignment()
.getCodonFrames();
ColumnSelection mappedColumns = new ColumnSelection();
+
+ if (colsel == null)
+ {
+ return mappedColumns;
+ }
+
char fromGapChar = mapFrom.getAlignment().getGapCharacter();
// FIXME allow for hidden columns
assertEquals("[0, 1, 3]", cs.getSelected().toString());
}
+ @Test
+ public void testMapColumnSelection_null() throws IOException
+ {
+ setupMappedAlignments();
+ ColumnSelection cs = MappingUtils.mapColumnSelection(null, dnaView,
+ proteinView);
+ assertTrue("mapped selection not empty", cs.getSelected().isEmpty());
+ }
+
/**
* Tests for the method that converts a series of [start, end] ranges to
* single positions