+/* vim: set ts=2: */
+/**
+ * Copyright (c) 2006 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions, and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * 3. Redistributions must acknowledge that this software was
+ * originally developed by the UCSF Computer Graphics Laboratory
+ * under support by the NIH National Center for Research Resources,
+ * grant P41-RR01081.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
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.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
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.
*/
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;
this.structureManager = structureManager;
chimera = null;
chimeraListenerThread = null;
- currentModelsMap = new HashMap<Integer, ChimeraModel>();
+ currentModelsMap = new HashMap<>();
}
public List<ChimeraModel> getChimeraModels(String modelName,
ModelType modelType)
{
- List<ChimeraModel> models = new ArrayList<ChimeraModel>();
+ List<ChimeraModel> models = new ArrayList<>();
for (ChimeraModel model : currentModelsMap.values())
{
if (modelName.equals(model.getModelName())
public Map<String, List<ChimeraModel>> getChimeraModelsMap()
{
- Map<String, List<ChimeraModel>> models = new HashMap<String, List<ChimeraModel>>();
+ Map<String, List<ChimeraModel>> models = new HashMap<>();
for (ChimeraModel model : currentModelsMap.values())
{
String modelName = model.getModelName();
return hasChimeraModel(modelNubmer, 0);
}
- public boolean hasChimeraModel(Integer modelNubmer, Integer subModelNumber)
+ public boolean hasChimeraModel(Integer modelNubmer,
+ Integer subModelNumber)
{
- return currentModelsMap.containsKey(ChimUtils.makeModelKey(modelNubmer,
- subModelNumber));
+ return currentModelsMap.containsKey(
+ ChimUtils.makeModelKey(modelNubmer, subModelNumber));
}
public void addChimeraModel(Integer modelNumber, Integer subModelNumber,
ChimUtils.makeModelKey(modelNumber, subModelNumber), model);
}
- public void removeChimeraModel(Integer modelNumber, Integer subModelNumber)
+ public void removeChimeraModel(Integer modelNumber,
+ Integer subModelNumber)
{
int modelKey = ChimUtils.makeModelKey(modelNumber, subModelNumber);
if (currentModelsMap.containsKey(modelKey))
{
logger.info("chimera open " + modelPath);
// 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
- // TODO: find a variant that works for sub-models
- 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 = isChimeraX() ? null : getModelColor(chimeraModel);
if (modelColor != null)
{
- newModel.setModelColor(modelColor);
+ chimeraModel.setModelColor(modelColor);
}
// Get our properties (default color scheme, etc.)
// chimeraSend("repr stick "+newModel.toSpec());
// Create the information we need for the navigator
- if (type != ModelType.SMILES)
+ if (type != ModelType.SMILES && !isChimeraX())
{
- addResidues(newModel);
+ addResidues(chimeraModel);
}
}
sendChimeraCommand("focus", false);
// startListening(); // see ChimeraListener
- return models;
+ 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;
}
{
sendChimeraCommand("close " + model.toSpec(), false);
// currentModelNamesMap.remove(model.getModelName());
- currentModelsMap.remove(ChimUtils.makeModelKey(
- model.getModelNumber(), model.getSubModelNumber()));
+ currentModelsMap.remove(ChimUtils.makeModelKey(model.getModelNumber(),
+ model.getSubModelNumber()));
// selectionList.remove(chimeraModel);
}
else
public void startListening()
{
- sendChimeraCommand("listen start models; listen start selection", false);
+ sendChimeraCommand("listen start models; listen start selection",
+ false);
}
public void stopListening()
{
- sendChimeraCommand("listen stop models ; listen stop selection ", false);
+ String command = "listen stop models ; listen stop selection ";
+ sendChimeraCommand(command, false);
}
/**
*/
public void startListening(String uri)
{
- sendChimeraCommand("listen start models url " + uri
- + ";listen start select prefix SelectionChanged url " + uri, false);
+ /*
+ * listen for model changes
+ */
+ String command = "listen start models url " + uri;
+ sendChimeraCommand(command, false);
+
+ /*
+ * listen for selection changes
+ */
+ command = "listen start select prefix SelectionChanged url " + uri;
+ sendChimeraCommand(command, false);
}
/**
{
chimera = null;
currentModelsMap.clear();
- this.chimeraRestPort = 0;
+ this.chimeraRestPort = 0;
structureManager.clearOnChimeraExit();
}
sendChimeraCommand("stop really", false);
try
{
+ // TODO is this too violent? could it force close the process
+ // before it has done an orderly shutdown?
chimera.destroy();
} catch (Exception ex)
{
public Map<Integer, ChimeraModel> getSelectedModels()
{
- Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<Integer, ChimeraModel>();
+ Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<>();
List<String> chimeraReply = sendChimeraCommand(
"list selection level molecule", true);
if (chimeraReply != null)
*/
public List<String> getSelectedResidueSpecs()
{
- List<String> selectedResidues = new ArrayList<String>();
- List<String> chimeraReply = sendChimeraCommand(
- "list selection level residue", true);
+ List<String> selectedResidues = new ArrayList<>();
+
+ String command = "list selection level residue";
+ List<String> chimeraReply = sendChimeraCommand(command, true);
if (chimeraReply != null)
{
+ /*
+ * expect 0, 1 or more lines of the format either
+ * Chimera:
+ * residue id #0:43.A type GLY
+ * ChimeraX:
+ * residue id /A:89 name THR index 88
+ * We are only interested in the atomspec (third token of the reply)
+ */
for (String inputLine : chimeraReply)
{
String[] inputLineParts = inputLine.split("\\s+");
- if (inputLineParts.length == 5)
+ if (inputLineParts.length >= 5)
{
selectedResidues.add(inputLineParts[2]);
}
// TODO: [Optional] Handle smiles names in a better way in Chimera?
public List<ChimeraModel> getModelList()
{
- List<ChimeraModel> modelList = new ArrayList<ChimeraModel>();
- List<String> list = sendChimeraCommand("list models type molecule",
- true);
+ List<ChimeraModel> modelList = new ArrayList<>();
+ String command = "list models type "
+ + (isChimeraX() ? "AtomicStructure" : "molecule");
+ List<String> list = sendChimeraCommand(command, true);
if (list != null)
{
for (String modelLine : list)
{
- ChimeraModel chimeraModel = new ChimeraModel(modelLine);
- modelList.add(chimeraModel);
+ try
+ {
+ ChimeraModel chimeraModel = new ChimeraModel(modelLine);
+ modelList.add(chimeraModel);
+ } catch (NullPointerException e)
+ {
+ // hack for now
+ }
}
}
return modelList;
*/
public List<String> getPresets()
{
- ArrayList<String> presetList = new ArrayList<String>();
+ ArrayList<String> presetList = new ArrayList<>();
List<String> output = sendChimeraCommand("preset list", true);
if (output != null)
{
// iterate over possible paths for starting Chimera
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";
- continue;
- }
try
{
- List<String> args = new ArrayList<String>();
+ // ensure symbolic links are resolved
+ chimeraPath = Paths.get(chimeraPath).toRealPath().toString();
+ 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";
+ continue;
+ }
+ List<String> args = new ArrayList<>();
args.add(chimeraPath);
// shows Chimera output window but suppresses REST responses:
// args.add("--debug");
- args.add("--start");
- args.add("RESTServer");
+ addLaunchArguments(args);
ProcessBuilder pb = new ProcessBuilder(args);
chimera = pb.start();
error = "";
break;
} catch (Exception e)
{
- // Chimera could not be started
+ // Chimera could not be started using this path
error += e.getMessage();
}
}
if (error.length() == 0)
{
this.chimeraRestPort = getPortNumber();
- System.out.println("Chimera REST API started on port "
- + chimeraRestPort);
+ 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
}
/**
+ * Adds command-line arguments to start the REST server
+ * <p>
+ * Method extracted for Jalview to allow override in ChimeraXManager
+ *
+ * @param args
+ */
+ protected void addLaunchArguments(List<String> args)
+ {
+ args.add("--start");
+ args.add("RESTServer");
+ }
+
+ /**
* 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));
+ BufferedReader lineReader = new BufferedReader(
+ new InputStreamReader(readChan));
StringBuilder responses = new StringBuilder();
try
{
{
responses.append("\n" + response);
// expect: REST server on host 127.0.0.1 port port_number
+ // ChimeraX is the same except "REST server started on host..."
if (response.startsWith("REST server"))
{
String[] tokens = response.split(" ");
- if (tokens.length == 7 && "port".equals(tokens[5]))
+ for (int i = 0; i < tokens.length - 1; i++)
{
- port = Integer.parseInt(tokens[6]);
- break;
+ if ("port".equals(tokens[i]))
+ {
+ port = Integer.parseInt(tokens[i + 1]);
+ break;
+ }
}
}
+ if (port > 0)
+ {
+ break; // hack for hanging readLine()
+ }
response = lineReader.readLine();
}
} catch (Exception e)
{
- logger.error("Failed to get REST port number from " + responses
- + ": "
+ logger.error("Failed to get REST port number from " + responses + ": "
+ e.getMessage());
} finally
{
}
if (port == 0)
{
- System.err
- .println("Failed to start Chimera with REST service, response was: "
+ System.err.println(
+ "Failed to start Chimera with REST service, response was: "
+ responses);
}
- logger.info("Chimera REST service listening on port " + chimeraRestPort);
+ logger.info(
+ "Chimera REST service listening on port " + chimeraRestPort);
return port;
}
public List<String> getAttrList()
{
- List<String> attributes = new ArrayList<String>();
- final List<String> reply = sendChimeraCommand("list resattr", true);
+ List<String> attributes = new ArrayList<>();
+ String command = (isChimeraX() ? "info " : "list ") + "resattr";
+ final List<String> reply = sendChimeraCommand(command, true);
if (reply != null)
{
for (String inputLine : reply)
public Map<ChimeraResidue, Object> getAttrValues(String aCommand,
ChimeraModel model)
{
- Map<ChimeraResidue, Object> values = new HashMap<ChimeraResidue, Object>();
+ Map<ChimeraResidue, Object> values = new HashMap<>();
final List<String> reply = sendChimeraCommand("list residue spec "
+ model.toSpec() + " attribute " + aCommand, true);
if (reply != null)
String[] lineParts = inputLine.split("\\s");
if (lineParts.length == 5)
{
- ChimeraResidue residue = ChimUtils
- .getResidue(lineParts[2], model);
+ ChimeraResidue residue = ChimUtils.getResidue(lineParts[2],
+ model);
String value = lineParts[4];
if (residue != null)
{
*/
public List<String> sendChimeraCommand(String command, boolean reply)
{
-// System.out.println("chimeradebug>> " + command);
+ if (debug)
+ {
+ System.out.println("chimeradebug>> " + command);
+ }
if (!isChimeraLaunched() || command == null
|| "".equals(command.trim()))
{
return null;
}
- // TODO do we need a maximum wait time before aborting?
- while (busy)
+ /*
+ * set a maximum wait time before trying anyway
+ * to avoid hanging indefinitely
+ */
+ int waited = 0;
+ int pause = 25;
+ while (busy && waited < 1001)
{
try
{
- Thread.sleep(25);
+ Thread.sleep(pause);
+ waited += pause;
} catch (InterruptedException q)
{
}
+ (System.currentTimeMillis() - startTime) + "ms: "
+ command);
}
-
}
}
protected List<String> sendRestCommand(String command)
{
String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
- List<NameValuePair> commands = new ArrayList<NameValuePair>(1);
+ List<NameValuePair> commands = new ArrayList<>(1);
+ String method = getHttpRequestMethod();
+ if ("GET".equals(method))
+ {
+ command = command.replace(" ", "+").replace("#", "%23")
+ .replace("|", "%7C").replace(";", "%3B");
+ }
commands.add(new BasicNameValuePair("command", command));
- List<String> reply = new ArrayList<String>();
+ List<String> reply = new ArrayList<>();
BufferedReader response = null;
- try {
- response = HttpClientUtils
- .doHttpUrlPost(restUrl, commands, 100, 5000);
+ try
+ {
+ response = "GET".equals(method)
+ ? HttpClientUtils.doHttpGet(restUrl, commands,
+ CONNECTION_TIMEOUT_MS, REST_REPLY_TIMEOUT_MS)
+ : HttpClientUtils.doHttpUrlPost(restUrl, commands,
+ CONNECTION_TIMEOUT_MS, REST_REPLY_TIMEOUT_MS);
String line = "";
- while ((line = response.readLine()) != null) {
+ while ((line = response.readLine()) != null)
+ {
reply.add(line);
}
} catch (Exception e)
{
- logger.error("REST call " + command + " failed: " + e.getMessage());
+ logger.error("REST call '" + command + "' failed: " + e.getMessage());
} finally
{
if (response != null)
}
/**
+ * Returns "POST" as the HTTP request method to use for REST service calls to
+ * Chimera
+ *
+ * @return
+ */
+ protected String getHttpRequestMethod()
+ {
+ return "POST";
+ }
+
+ /**
* Send a command to stdin of Chimera process, and optionally read any
* responses.
*
{
return busy;
}
+
+ public Process getChimeraProcess()
+ {
+ return chimera;
+ }
+
+ public boolean isChimeraX()
+ {
+ return false;
+ }
}