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;
*/
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<Integer, ChimeraModel> currentModelsMap;
{
this.structureManager = structureManager;
chimera = null;
- chimeraListenerThreads = null;
+ chimeraListenerThread = null;
currentModelsMap = new HashMap<Integer, ChimeraModel>();
}
{
chimera = null;
currentModelsMap.clear();
- chimeraListenerThreads = null;
+ if (!USE_REST)
+ {
+ chimeraListenerThread.requestStop();
+ chimeraListenerThread = null;
+ }
structureManager.clearOnChimeraExit();
}
List<String> args = new ArrayList<String>();
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)
{
// 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
}
/**
+ * 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
;
}
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<String> 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<String> 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<NameValuePair> commands = new ArrayList<NameValuePair>(1);
+ commands.add(new BasicNameValuePair("command", command));
+
+ List<String> reply = new ArrayList<String>();
+ 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<String> 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<String> rsp = chimeraListenerThread.getResponse(command);
+ return rsp;
}
public StructureManager getStructureManager()
/**
* Reply listener thread
*/
-public class ListenerThreads extends Thread {
- private InputStream readChan = null;
- private BufferedReader lineReader = null;
- private Process chimera = null;
- private Map<String, List<String>> 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<String, List<String>>();
- // 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<String> getResponse(String command) {
- List<String> reply;
- // System.out.println("getResponse: "+command);
+public class ListenerThreads extends Thread
+{
+ private BufferedReader lineReader = null;
+
+ private Process chimera = null;
+
+ private Map<String, List<String>> 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<String, List<String>>();
+ // 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<String> getResponse(String command)
+ {
+ List<String> 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<String> reply = new ArrayList<String>();
- 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<String> reply = new ArrayList<String>();
+ 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;
+ }
}
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
{
ArrayList<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
- Hashtable<String,StringBuffer> colranges=new Hashtable<String,StringBuffer>();
+
+ /*
+ * Map of { colour, positionSpecs}
+ */
+ Map<String, StringBuilder> colranges = new LinkedHashMap<String, StringBuilder>();
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<String> str = new ArrayList<String>();
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++)
{
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))
lastCol = null;
startPos = pos;
startModel = pdbfnum;
- startChain = mapping[m].getChain();
}
lastCol = col;
lastPos = pos;
}
}
// 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)+";");
return cset.toArray(new StructureMappingcommandSet[cset.size()]);
}
- private static void addColourRange(Hashtable<String, StringBuffer> 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<String, StringBuilder> 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);
}
}