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 = false;
+
+ private int chimeraRestPort;
- static private Process chimera;
+ private Process chimera;
- static private ListenerThreads chimeraListenerThreads;
+ private ListenerThreads chimeraListenerThread;
- static private Map<Integer, ChimeraModel> currentModelsMap;
+ private Map<Integer, ChimeraModel> currentModelsMap;
- private static Logger logger = LoggerFactory
+ private Logger logger = LoggerFactory
.getLogger(ext.edu.ucsf.rbvi.strucviz2.ChimeraManager.class);
private StructureManager structureManager;
{
this.structureManager = structureManager;
chimera = null;
- chimeraListenerThreads = null;
+ chimeraListenerThread = null;
currentModelsMap = new HashMap<Integer, ChimeraModel>();
-
+
}
public List<ChimeraModel> getChimeraModels(String modelName)
public List<ChimeraModel> openModel(String modelPath, ModelType type)
{
+ return openModel(modelPath, getFileNameFromPath(modelPath), type);
+ }
+
+ /**
+ * Overloaded method to allow Jalview to pass in a model name.
+ *
+ * @param modelPath
+ * @param modelName
+ * @param type
+ * @return
+ */
+ public List<ChimeraModel> openModel(String modelPath, String modelName,
+ ModelType type)
+ {
logger.info("chimera open " + modelPath);
stopListening();
List<String> response = null;
{
continue;
}
- String modelName = modelPath;
- // TODO: [Optional] Convert path to name in a better way
- if (modelPath.lastIndexOf(File.separator) > 0)
- {
- modelName = modelPath.substring(modelPath
- .lastIndexOf(File.separator) + 1);
- }
- else if (modelPath.lastIndexOf("/") > 0)
- {
- modelName = modelPath
- .substring(modelPath.lastIndexOf("/") + 1);
- }
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;
}
}
return models;
}
+ /**
+ * Refactored method to extract the last (or only) element delimited by file
+ * path separator.
+ *
+ * @param modelPath
+ * @return
+ */
+ private String getFileNameFromPath(String modelPath)
+ {
+ String modelName = modelPath;
+ if (modelPath == null)
+ {
+ return null;
+ }
+ // TODO: [Optional] Convert path to name in a better way
+ if (modelPath.lastIndexOf(File.separator) > 0)
+ {
+ modelName = modelPath.substring(modelPath
+ .lastIndexOf(File.separator) + 1);
+ }
+ else if (modelPath.lastIndexOf("/") > 0)
+ {
+ modelName = modelPath
+ .substring(modelPath.lastIndexOf("/") + 1);
+ }
+ return modelName;
+ }
+
public void closeModel(ChimeraModel model)
{
// int model = structure.modelNumber();
{
chimera = null;
currentModelsMap.clear();
- chimeraListenerThreads = null;
+ this.chimeraRestPort = 0;
structureManager.clearOnChimeraExit();
}
public boolean isChimeraLaunched()
{
- // TODO: [Optional] What is the best way to test if chimera is launched?
-
- // sendChimeraCommand("test", true) !=null
+ boolean launched = false;
if (chimera != null)
{
- return true;
+ try
+ {
+ chimera.exitValue();
+ // if we get here, process has ended
+ } catch (IllegalThreadStateException e)
+ {
+ // ok - not yet terminated
+ launched = true;
+ }
}
- return false;
+ return launched;
}
public boolean launchChimera(List<String> chimeraPaths)
List<String> args = new ArrayList<String>();
args.add(chimeraPath);
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
}
/**
+ * 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 "
+ + chimeraRestPort);
+ }
+ } 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
{
return null;
}
- return ChimUtils.parseModelColor((String) colorLines.get(0));
+ return ChimUtils.parseModelColor(colorLines.get(0));
}
/**
return values;
}
+ private volatile boolean busy = false;
+
/**
* Send a command to Chimera.
*
*/
public List<String> sendChimeraCommand(String command, boolean reply)
{
- if (!isChimeraLaunched())
+ if (!isChimeraLaunched() || command == null
+ || "".equals(command.trim()))
{
return null;
}
+ // TODO do we need a maximum wait time before aborting?
+ while (busy)
+ {
+ try
+ {
+ Thread.sleep(25);
+ } catch (InterruptedException q)
+ {
+ }
+ }
+ busy = true;
+ long startTime = System.currentTimeMillis();
+ try
+ {
+ return sendRestCommand(command);
+ } finally
+ {
+ /*
+ * Make sure busy flag is reset come what may!
+ */
+ busy = false;
+ if (debug)
+ {
+ System.out.println("Chimera command took "
+ + (System.currentTimeMillis() - startTime) + "ms: "
+ + command);
+ }
- chimeraListenerThreads.clearResponse(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.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);
+ 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");
- // System.out.println("send command to chimera: " + text);
try
{
// send the command
clearOnChimeraExit();
return null;
}
- if (!reply)
+ if (!readReply)
{
return null;
}
- return chimeraListenerThreads.getResponse(command);
+ List<String> rsp = chimeraListenerThread.getResponse(command);
+ return rsp;
+ }
+
+ public StructureManager getStructureManager()
+ {
+ return structureManager;
+ }
+
+ public boolean isBusy()
+ {
+ return busy;
}
}