From 2dd1164b5450a3b1b3b17420543fadece6e5fcbe Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 28 Nov 2014 12:19:58 +0000 Subject: [PATCH] JAL-1596 first REST interface coded --- .../edu/ucsf/rbvi/strucviz2/ChimeraManager.java | 189 ++++++-- .../ucsf/rbvi/strucviz2/port/ListenerThreads.java | 451 ++++++++++++-------- src/jalview/ext/rbvi/chimera/ChimeraCommands.java | 60 ++- 3 files changed, 458 insertions(+), 242 deletions(-) diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java index a7440e7..f65f00b 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java @@ -1,14 +1,21 @@ package ext.edu.ucsf.rbvi.strucviz2; +import jalview.ws.HttpClientUtils; + import java.awt.Color; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.http.NameValuePair; +import org.apache.http.message.BasicNameValuePair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,10 +27,19 @@ import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads; */ public class ChimeraManager { + private static final boolean debug = true; + + /* + * true: use REST API (recommended), false: use stdout/stdin (deprecated) + */ + private static final boolean USE_REST = true; + + // Port number for Chimera REST service + private int restPort; private Process chimera; - private ListenerThreads chimeraListenerThreads; + private ListenerThreads chimeraListenerThread; private Map currentModelsMap; @@ -36,7 +52,7 @@ public class ChimeraManager { this.structureManager = structureManager; chimera = null; - chimeraListenerThreads = null; + chimeraListenerThread = null; currentModelsMap = new HashMap(); } @@ -354,7 +370,11 @@ public class ChimeraManager { chimera = null; currentModelsMap.clear(); - chimeraListenerThreads = null; + if (!USE_REST) + { + chimeraListenerThread.requestStop(); + chimeraListenerThread = null; + } structureManager.clearOnChimeraExit(); } @@ -525,12 +545,13 @@ public class ChimeraManager List args = new ArrayList(); args.add(chimeraPath); args.add("--start"); - args.add("ReadStdin"); + args.add(USE_REST ? "RESTServer" : "ReadStdin"); ProcessBuilder pb = new ProcessBuilder(args); chimera = pb.start(); error = ""; workingPath = chimeraPath; - logger.info("Strarting " + chimeraPath); + logger.info("Starting " + chimeraPath + " with " + + (USE_REST ? "REST API" : "stdin/stdout")); break; } catch (Exception e) { @@ -541,10 +562,17 @@ public class ChimeraManager // If no error, then Chimera was launched successfully if (error.length() == 0) { - // Initialize the listener threads - chimeraListenerThreads = new ListenerThreads(chimera, - structureManager); - chimeraListenerThreads.start(); + if (USE_REST) + { + this.restPort = getPortNumber(); + } + else + { + // Initialize the listener threads + chimeraListenerThread = new ListenerThreads(chimera, + structureManager); + chimeraListenerThread.start(); + } // structureManager.initChimTable(); structureManager.setChimeraPathProperty(workingPath); // TODO: [Optional] Check Chimera version and show a warning if below 1.8 @@ -559,6 +587,41 @@ public class ChimeraManager } /** + * Read and return the port number returned in the reply to --start RESTServer + */ + private int getPortNumber() + { + int port = 0; + InputStream readChan = chimera.getInputStream(); + BufferedReader lineReader = new BufferedReader(new InputStreamReader( + readChan)); + String response = null; + try + { + // expect: REST server on host 127.0.0.1 port port_number + response = lineReader.readLine(); + String [] tokens = response.split(" "); + if (tokens.length == 7 && "port".equals(tokens[5])) { + port = Integer.parseInt(tokens[6]); + logger.info("Chimera REST service listening on port " + restPort); + } + } catch (Exception e) + { + logger.error("Failed to get REST port number from " + response + ": " + + e.getMessage()); + } finally + { + try + { + lineReader.close(); + } catch (IOException e2) + { + } + } + return port; + } + + /** * Determine the color that Chimera is using for this model. * * @param model @@ -699,38 +762,102 @@ public class ChimeraManager ; } busy = true; + long startTime = System.currentTimeMillis(); try { - chimeraListenerThreads.clearResponse(command); - String text = command.concat("\n"); - // System.out.println("send command to chimera: " + text); - try + if (USE_REST) { - // send the command - chimera.getOutputStream().write(text.getBytes()); - chimera.getOutputStream().flush(); - } catch (IOException e) - { - // logger.info("Unable to execute command: " + text); - // logger.info("Exiting..."); - logger.warn("Unable to execute command: " + text); - logger.warn("Exiting..."); - clearOnChimeraExit(); - // busy = false; - return null; + return sendRestCommand(command); } - if (!reply) + else { - // busy = false; - return null; + return sendStdinCommand(command, reply); } - List rsp = chimeraListenerThreads.getResponse(command); - // busy = false; - return rsp; } finally { busy = false; + if (debug) + { + System.out.println("Chimera command took " + + (System.currentTimeMillis() - startTime) + "ms: " + + command); + } + + } + } + + /** + * Sends the command to Chimera's REST API, and returns any response lines. + * + * @param command + * @return + */ + protected List sendRestCommand(String command) + { + // TODO start a separate thread to do this so we don't block? + String restUrl = "http://127.0.0.1:" + this.restPort + "/run"; + List commands = new ArrayList(1); + commands.add(new BasicNameValuePair("command", command)); + + List reply = new ArrayList(); + BufferedReader response = null; + try { + response = HttpClientUtils.doHttpUrlPost(restUrl, + commands); + String line = ""; + while ((line = response.readLine()) != null) { + reply.add(line); + } + } catch (Exception e) + { + logger.error("REST call " + command + " failed: " + e.getMessage()); + } finally + { + if (response != null) + { + try + { + response.close(); + } catch (IOException e) + { + } + } + } + return reply; + } + + /** + * Send a command to stdin of Chimera process, and optionally read any + * responses. + * + * @param command + * @param readReply + * @return + */ + protected List sendStdinCommand(String command, boolean readReply) + { + chimeraListenerThread.clearResponse(command); + String text = command.concat("\n"); + try + { + // send the command + chimera.getOutputStream().write(text.getBytes()); + chimera.getOutputStream().flush(); + } catch (IOException e) + { + // logger.info("Unable to execute command: " + text); + // logger.info("Exiting..."); + logger.warn("Unable to execute command: " + text); + logger.warn("Exiting..."); + clearOnChimeraExit(); + return null; + } + if (!readReply) + { + return null; } + List rsp = chimeraListenerThread.getResponse(command); + return rsp; } public StructureManager getStructureManager() diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java b/src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java index 883d536..2b2ce48 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java @@ -21,207 +21,282 @@ import ext.edu.ucsf.rbvi.strucviz2.StructureManager; /** * Reply listener thread */ -public class ListenerThreads extends Thread { - private InputStream readChan = null; - private BufferedReader lineReader = null; - private Process chimera = null; - private Map> replyLog = null; - private Logger logger; - private StructureManager structureManager = null; - - /** - * Create a new listener thread to read the responses from Chimera - * - * @param chimera - * a handle to the Chimera Process - * @param log - * a handle to a List to post the responses to - * @param chimeraObject - * a handle to the Chimera Object - */ - public ListenerThreads(Process chimera, StructureManager structureManager) { - this.chimera = chimera; - this.structureManager = structureManager; - replyLog = new HashMap>(); - // Get a line-oriented reader - readChan = chimera.getInputStream(); - lineReader = new BufferedReader(new InputStreamReader(readChan)); - logger = LoggerFactory.getLogger(ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads.class); - } - - /** - * Start the thread running - */ - public void run() { - // System.out.println("ReplyLogListener running"); - while (true) { - try { - chimeraRead(); - } catch (IOException e) { - logger.warn("UCSF Chimera has exited: " + e.getMessage()); - return; - } - } - } - - public List getResponse(String command) { - List reply; - // System.out.println("getResponse: "+command); +public class ListenerThreads extends Thread +{ + private BufferedReader lineReader = null; + + private Process chimera = null; + + private Map> replyLog = null; + + private Logger logger; + + private StructureManager structureManager = null; + + private boolean stopMe = false; + + /** + * Create a new listener thread to read the responses from Chimera + * + * @param chimera + * a handle to the Chimera Process + * @param structureManager + * a handle to the Chimera structure manager + */ + public ListenerThreads(Process chimera, StructureManager structureManager) + { + this.chimera = chimera; + this.structureManager = structureManager; + replyLog = new HashMap>(); + // Get a line-oriented reader + InputStream readChan = chimera.getInputStream(); + lineReader = new BufferedReader(new InputStreamReader(readChan)); + logger = LoggerFactory + .getLogger(ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads.class); + } + + /** + * Start the thread running + */ + public void run() + { + // System.out.println("ReplyLogListener running"); + while (!stopMe) + { + try + { + chimeraRead(); + } catch (IOException e) + { + logger.warn("UCSF Chimera has exited: " + e.getMessage()); + return; + } finally + { + if (lineReader != null) + { + try + { + lineReader.close(); + } catch (IOException e) + { + } + } + } + } + } + + public List getResponse(String command) + { + List reply; + // System.out.println("getResponse: "+command); // TODO do we need a maximum wait time before aborting? - while (!replyLog.containsKey(command)) { - try { - Thread.currentThread().sleep(100); - } catch (InterruptedException e) { - } - } - - synchronized (replyLog) { - reply = replyLog.get(command); - // System.out.println("getResponse ("+command+") = "+reply); - replyLog.remove(command); - } - return reply; - } - - public void clearResponse(String command) { - try { - Thread.currentThread().sleep(100); - } catch (InterruptedException e) { - } - if (replyLog.containsKey(command)) + while (!replyLog.containsKey(command)) + { + try + { + Thread.currentThread().sleep(100); + } catch (InterruptedException e) + { + } + } + + synchronized (replyLog) { + reply = replyLog.get(command); + // System.out.println("getResponse ("+command+") = "+reply); replyLog.remove(command); } - return; - } + return reply; + } - /** - * Read input from Chimera - * - * @return a List containing the replies from Chimera - */ - private void chimeraRead() throws IOException { - if (chimera == null) + public void clearResponse(String command) + { + try + { + Thread.currentThread().sleep(100); + } catch (InterruptedException e) + { + } + if (replyLog.containsKey(command)) + { + replyLog.remove(command); + } + return; + } + + /** + * Read input from Chimera + * + * @return a List containing the replies from Chimera + */ + private void chimeraRead() throws IOException + { + if (chimera == null) { return; } - String line = null; - while ((line = lineReader.readLine()) != null) { - // System.out.println("From Chimera-->" + line); - if (line.startsWith("CMD")) { - chimeraCommandRead(line.substring(4)); - } else if (line.startsWith("ModelChanged: ")) { - (new ModelUpdater()).start(); - } else if (line.startsWith("SelectionChanged: ")) { - (new SelectionUpdater()).start(); - } else if (line.startsWith("Trajectory residue network info:")) { - (new NetworkUpdater(line)).start(); - } - } - return; - } - - private void chimeraCommandRead(String command) throws IOException { - // Generally -- looking for: - // CMD command - // ........ - // END - // We return the text in between - List reply = new ArrayList(); - boolean updateModels = false; - boolean updateSelection = false; - boolean importNetwork = false; - String line = null; - - synchronized (replyLog) { - while ((line = lineReader.readLine()) != null) { - // System.out.println("From Chimera (" + command + ") -->" + line); - if (line.startsWith("CMD")) { - logger.warn("Got unexpected command from Chimera: " + line); - - } else if (line.startsWith("END")) { - break; - } - if (line.startsWith("ModelChanged: ")) { - updateModels = true; - } else if (line.startsWith("SelectionChanged: ")) { - updateSelection = true; - } else if (line.length() == 0) { - continue; - } else if (!line.startsWith("CMD")) { - reply.add(line); - } else if (line.startsWith("Trajectory residue network info:")) { - importNetwork = true; - } - } - replyLog.put(command, reply); - } - if (updateModels) + String line = null; + while ((line = lineReader.readLine()) != null) + { + // System.out.println("From Chimera-->" + line); + if (line.startsWith("CMD")) + { + chimeraCommandRead(line.substring(4)); + } + else if (line.startsWith("ModelChanged: ")) + { + (new ModelUpdater()).start(); + } + else if (line.startsWith("SelectionChanged: ")) + { + (new SelectionUpdater()).start(); + } + else if (line.startsWith("Trajectory residue network info:")) + { + (new NetworkUpdater(line)).start(); + } + } + return; + } + + private void chimeraCommandRead(String command) throws IOException + { + // Generally -- looking for: + // CMD command + // ........ + // END + // We return the text in between + List reply = new ArrayList(); + boolean updateModels = false; + boolean updateSelection = false; + boolean importNetwork = false; + String line = null; + + synchronized (replyLog) + { + while ((line = lineReader.readLine()) != null) + { + // System.out.println("From Chimera (" + command + ") -->" + line); + if (line.startsWith("CMD")) + { + logger.warn("Got unexpected command from Chimera: " + line); + + } + else if (line.startsWith("END")) + { + break; + } + if (line.startsWith("ModelChanged: ")) + { + updateModels = true; + } + else if (line.startsWith("SelectionChanged: ")) + { + updateSelection = true; + } + else if (line.length() == 0) + { + continue; + } + else if (!line.startsWith("CMD")) + { + reply.add(line); + } + else if (line.startsWith("Trajectory residue network info:")) + { + importNetwork = true; + } + } + replyLog.put(command, reply); + } + if (updateModels) { (new ModelUpdater()).start(); } - if (updateSelection) + if (updateSelection) { (new SelectionUpdater()).start(); } - if (importNetwork) { - (new NetworkUpdater(line)).start(); - } - return; - } - - /** - * Model updater thread - */ - class ModelUpdater extends Thread { - - public ModelUpdater() { - } - - public void run() { - structureManager.updateModels(); - structureManager.modelChanged(); - } - } - - /** - * Selection updater thread - */ - class SelectionUpdater extends Thread { - - public SelectionUpdater() { - } - - public void run() { - try { - logger.info("Responding to chimera selection"); - structureManager.chimeraSelectionChanged(); - } catch (Exception e) { - logger.warn("Could not update selection", e); - } - } - } - - /** - * Selection updater thread - */ - class NetworkUpdater extends Thread { - - private String line; - - public NetworkUpdater(String line) { - this.line = line; - } - - public void run() { - try { -// ((TaskManager) structureManager.getService(TaskManager.class)) -// .execute(new ImportTrajectoryRINTaskFactory(structureManager, line) -// .createTaskIterator()); - } catch (Exception e) { - logger.warn("Could not import trajectory network", e); - } - } - } + if (importNetwork) + { + (new NetworkUpdater(line)).start(); + } + return; + } + + /** + * Model updater thread + */ + class ModelUpdater extends Thread + { + + public ModelUpdater() + { + } + + public void run() + { + structureManager.updateModels(); + structureManager.modelChanged(); + } + } + + /** + * Selection updater thread + */ + class SelectionUpdater extends Thread + { + + public SelectionUpdater() + { + } + + public void run() + { + try + { + logger.info("Responding to chimera selection"); + structureManager.chimeraSelectionChanged(); + } catch (Exception e) + { + logger.warn("Could not update selection", e); + } + } + } + + /** + * Selection updater thread + */ + class NetworkUpdater extends Thread + { + + private String line; + + public NetworkUpdater(String line) + { + this.line = line; + } + + public void run() + { + try + { + // ((TaskManager) structureManager.getService(TaskManager.class)) + // .execute(new ImportTrajectoryRINTaskFactory(structureManager, line) + // .createTaskIterator()); + } catch (Exception e) + { + logger.warn("Could not import trajectory network", e); + } + } + } + + /** + * Set a flag that this thread should clean up and exit. + */ + public void requestStop() + { + this.stopMe = true; + } } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index d3c8c09..4ee74aa 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -22,17 +22,16 @@ package jalview.ext.rbvi.chimera; import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; -import jalview.api.structures.JalviewStructureDisplayI; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; -import jalview.util.Format; import java.awt.Color; import java.util.ArrayList; -import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.Map; /** * Routines for generating Chimera commands for Jalview/Chimera binding @@ -57,20 +56,22 @@ public class ChimeraCommands { ArrayList cset = new ArrayList(); - Hashtable colranges=new Hashtable(); + + /* + * Map of { colour, positionSpecs} + */ + Map colranges = new LinkedHashMap(); for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { - float cols[] = new float[4]; StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); - StringBuffer command = new StringBuffer(); - StructureMappingcommandSet smc; - ArrayList str = new ArrayList(); if (mapping == null || mapping.length < 1) + { continue; + } int startPos = -1, lastPos = -1, startModel = -1, lastModel = -1; - String startChain = "", lastChain = ""; + String lastChain = ""; Color lastCol = null; for (int s = 0; s < sequence[pdbfnum].length; s++) { @@ -90,12 +91,16 @@ public class ChimeraCommands int pos = mapping[m].getPDBResNum(asp.findPosition(r)); if (pos < 1 || pos == lastPos) + { continue; + } Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r); if (fr != null) + { col = fr.findFeatureColour(col, sequence[pdbfnum][s], r); + } if (lastCol != col || lastPos + 1 != pos || pdbfnum != lastModel || !mapping[m].getChain().equals(lastChain)) @@ -107,7 +112,6 @@ public class ChimeraCommands lastCol = null; startPos = pos; startModel = pdbfnum; - startChain = mapping[m].getChain(); } lastCol = col; lastPos = pos; @@ -124,7 +128,7 @@ public class ChimeraCommands } } // Finally, add the command set ready to be returned. - StringBuffer coms=new StringBuffer(); + StringBuilder coms = new StringBuilder(256); for (String cr:colranges.keySet()) { coms.append("color #"+cr+" "+colranges.get(cr)+";"); @@ -135,24 +139,34 @@ public class ChimeraCommands return cset.toArray(new StructureMappingcommandSet[cset.size()]); } - private static void addColourRange(Hashtable colranges, Color lastCol, int startModel, - int startPos, int lastPos, String lastChain) + /** + * Helper method to record a range of positions of the same colour. + * + * @param colranges + * @param colour + * @param model + * @param startPos + * @param endPos + * @param chain + */ + private static void addColourRange(Map colranges, + Color colour, int model, + int startPos, int endPos, String chain) { - - String colstring = ((lastCol.getRed()< 16) ? "0":"")+Integer.toHexString(lastCol.getRed()) - + ((lastCol.getGreen()< 16) ? "0":"")+Integer.toHexString(lastCol.getGreen()) - + ((lastCol.getBlue()< 16) ? "0":"")+Integer.toHexString(lastCol.getBlue()); - StringBuffer currange = colranges.get(colstring); - if (currange==null) + String colstring = ((colour.getRed()< 16) ? "0":"")+Integer.toHexString(colour.getRed()) + + ((colour.getGreen()< 16) ? "0":"")+Integer.toHexString(colour.getGreen()) + + ((colour.getBlue()< 16) ? "0":"")+Integer.toHexString(colour.getBlue()); + StringBuilder currange = colranges.get(colstring); + if (currange == null) { - colranges.put(colstring,currange = new StringBuffer()); + colranges.put(colstring, currange = new StringBuilder(256)); } - if (currange.length()>0) + if (currange.length() > 0) { currange.append("|"); } - currange.append("#" + startModel + ":" + ((startPos==lastPos) ? startPos : startPos + "-" - + lastPos) + "." + lastChain); + currange.append("#" + model + ":" + ((startPos==endPos) ? startPos : startPos + "-" + + endPos) + "." + chain); } } -- 1.7.10.2