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 int REST_REPLY_TIMEOUT_MS = 15000;
+
+ private static final int CONNECTION_TIMEOUT_MS = 100;
+
+ private static final boolean debug = false;
+
+ private int chimeraRestPort;
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>();
}
ModelType type)
{
logger.info("chimera open " + modelPath);
- stopListening();
+ // stopListening();
+ List<ChimeraModel> modelList = getModelList();
List<String> response = null;
// TODO: [Optional] Handle modbase models
if (type == ModelType.MODBASE_MODEL)
logger.warn("Could not open " + modelPath);
return null;
}
- List<ChimeraModel> models = new ArrayList<ChimeraModel>();
- int[] modelNumbers = null;
- if (type == ModelType.PDB_MODEL)
- {
- for (String line : response)
- {
- if (line.startsWith("#"))
- {
- modelNumbers = ChimUtils.parseOpenedModelNumber(line);
- if (modelNumbers != null)
- {
- int modelNumber = ChimUtils.makeModelKey(modelNumbers[0],
- modelNumbers[1]);
- if (currentModelsMap.containsKey(modelNumber))
- {
- continue;
- }
- ChimeraModel newModel = new ChimeraModel(modelName, type,
- modelNumbers[0], modelNumbers[1]);
- currentModelsMap.put(modelNumber, newModel);
- models.add(newModel);
- // patch for Jalview - set model name in Chimera
- sendChimeraCommand("setattr M name " + modelName + " #"
- + modelNumbers[0], false);
- // end patch for Jalview
- modelNumbers = null;
- }
- }
- }
- }
- else
+
+ // patch for Jalview - set model name in Chimera
+ // TODO: find a variant that works for sub-models
+ for (ChimeraModel newModel : getModelList())
{
- // TODO: [Optional] Open smiles from file would fail. Do we need it?
- // If parsing fails, iterate over all open models to get the right one
- List<ChimeraModel> openModels = getModelList();
- for (ChimeraModel openModel : openModels)
+ if (!modelList.contains(newModel))
{
- String openModelName = openModel.getModelName();
- if (openModelName.endsWith("..."))
- {
- openModelName = openModelName.substring(0,
- openModelName.length() - 3);
- }
- if (modelPath.startsWith(openModelName))
- {
- openModel.setModelName(modelPath);
- int modelNumber = ChimUtils
- .makeModelKey(openModel.getModelNumber(),
- openModel.getSubModelNumber());
- if (!currentModelsMap.containsKey(modelNumber))
- {
- currentModelsMap.put(modelNumber, openModel);
- models.add(openModel);
- }
- }
+ newModel.setModelName(modelName);
+ sendChimeraCommand(
+ "setattr M name " + modelName + " #"
+ + newModel.getModelNumber(), false);
+ modelList.add(newModel);
}
}
// assign color and residues to open models
- for (ChimeraModel newModel : models)
+ for (ChimeraModel chimeraModel : modelList)
{
// get model color
- Color modelColor = getModelColor(newModel);
+ Color modelColor = getModelColor(chimeraModel);
if (modelColor != null)
{
- newModel.setModelColor(modelColor);
+ chimeraModel.setModelColor(modelColor);
}
// Get our properties (default color scheme, etc.)
// Create the information we need for the navigator
if (type != ModelType.SMILES)
{
- addResidues(newModel);
+ addResidues(chimeraModel);
}
}
sendChimeraCommand("focus", false);
- startListening();
- return models;
+ // startListening(); // see ChimeraListener
+ return modelList;
}
/**
// TODO: [Optional] Convert path to name in a better way
if (modelPath.lastIndexOf(File.separator) > 0)
{
- modelName = modelPath.substring(modelPath
- .lastIndexOf(File.separator) + 1);
+ modelName = modelPath
+ .substring(modelPath.lastIndexOf(File.separator) + 1);
}
else if (modelPath.lastIndexOf("/") > 0)
{
- modelName = modelPath
- .substring(modelPath.lastIndexOf("/") + 1);
+ modelName = modelPath.substring(modelPath.lastIndexOf("/") + 1);
}
return modelName;
}
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
+ + ";listen start select prefix SelectionChanged 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()
{
chimera = null;
currentModelsMap.clear();
- chimeraListenerThreads = null;
+ this.chimeraRestPort = 0;
structureManager.clearOnChimeraExit();
}
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>();
return launched;
}
+ /**
+ * Launch Chimera, unless an instance linked to this object is already
+ * running. Returns true if chimera is successfully launched, or already
+ * running, else false.
+ *
+ * @param chimeraPaths
+ * @return
+ */
public boolean launchChimera(List<String> chimeraPaths)
{
// Do nothing if Chimera is already launched
for (String chimeraPath : chimeraPaths)
{
File path = new File(chimeraPath);
+ // uncomment the next line to simulate Chimera not installed
+ // path = new File(chimeraPath + "x");
if (!path.canExecute())
{
error += "File '" + path + "' does not exist.\n";
{
List<String> args = new ArrayList<String>();
args.add(chimeraPath);
+ // shows Chimera output window but suppresses REST responses:
+ // args.add("--debug");
args.add("--start");
- args.add("ReadStdin");
+ args.add("RESTServer");
ProcessBuilder pb = new ProcessBuilder(args);
chimera = pb.start();
error = "";
workingPath = chimeraPath;
- logger.info("Strarting " + chimeraPath);
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();
+ this.chimeraRestPort = getPortNumber();
+ System.out.println("Chimera REST API started on port "
+ + chimeraRestPort);
// structureManager.initChimTable();
structureManager.setChimeraPathProperty(workingPath);
// TODO: [Optional] Check Chimera version and show a warning if below 1.8
// Ask Chimera to give us updates
- startListening();
- return true;
+ // startListening(); // later - see ChimeraListener
+ return (chimeraRestPort > 0);
}
// Tell the user that Chimera could not be started because of an error
}
/**
+ * 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));
+ StringBuilder responses = new StringBuilder();
+ try
+ {
+ String response = lineReader.readLine();
+ while (response != null)
+ {
+ responses.append("\n" + response);
+ // expect: REST server on host 127.0.0.1 port port_number
+ if (response.startsWith("REST server"))
+ {
+ String[] tokens = response.split(" ");
+ if (tokens.length == 7 && "port".equals(tokens[5]))
+ {
+ port = Integer.parseInt(tokens[6]);
+ break;
+ }
+ }
+ response = lineReader.readLine();
+ }
+ } catch (Exception e)
+ {
+ logger.error("Failed to get REST port number from " + responses
+ + ": " + e.getMessage());
+ } finally
+ {
+ try
+ {
+ lineReader.close();
+ } catch (IOException e2)
+ {
+ }
+ }
+ if (port == 0)
+ {
+ System.err
+ .println("Failed to start Chimera with REST service, response was: "
+ + responses);
+ }
+ logger.info("Chimera REST service listening on port " + chimeraRestPort);
+ return port;
+ }
+
+ /**
* Determine the color that Chimera is using for this model.
*
* @param model
*/
public List<String> sendChimeraCommand(String command, boolean reply)
{
- if (!isChimeraLaunched())
+ // System.out.println("chimeradebug>> " + command);
+ if (!isChimeraLaunched() || command == null
+ || "".equals(command.trim()))
{
return null;
}
} catch (InterruptedException q)
{
}
- ;
}
busy = true;
+ long startTime = System.currentTimeMillis();
try
{
- chimeraListenerThreads.clearResponse(command);
- String text = command.concat("\n");
- // System.out.println("send command to chimera: " + text);
- try
- {
- // send the command
- chimera.getOutputStream().write(text.getBytes());
- chimera.getOutputStream().flush();
- } catch (IOException e)
+ return sendRestCommand(command);
+ } finally
+ {
+ /*
+ * Make sure busy flag is reset come what may!
+ */
+ busy = false;
+ if (debug)
{
- // 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;
+ System.out.println("Chimera command took "
+ + (System.currentTimeMillis() - startTime) + "ms: "
+ + command);
}
- if (!reply)
+
+ }
+ }
+
+ /**
+ * Sends the command to Chimera's REST API, and returns any response lines.
+ *
+ * @param command
+ * @return
+ */
+ protected List<String> sendRestCommand(String command)
+ {
+ String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/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, CONNECTION_TIMEOUT_MS,
+ REST_REPLY_TIMEOUT_MS);
+ String line = "";
+ while ((line = response.readLine()) != null)
{
- // busy = false;
- return null;
+ reply.add(line);
}
- List<String> rsp = chimeraListenerThreads.getResponse(command);
- // busy = false;
- return rsp;
+ } catch (Exception e)
+ {
+ logger.error("REST call '" + command + "' failed: " + e.getMessage());
} finally
{
- busy = false;
+ 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()
{
return busy;
}
-
}