Merge branch 'develop' into JAL-1483_29dev
[jalview.git] / src / ext / edu / ucsf / rbvi / strucviz2 / ChimeraManager.java
index a7440e7..b45404e 100644 (file)
@@ -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 = false;
+
+  /*
+   * true: use REST API (recommended), false: use stdout/stdin (deprecated)
+   */
+  // TODO remove once definitely happy with using REST
+  private static final boolean USE_REST = true;
+
+  private int chimeraRestPort;
 
   private Process chimera;
 
-  private ListenerThreads chimeraListenerThreads;
+  private ListenerThreads chimeraListenerThread;
 
   private Map<Integer, ChimeraModel> currentModelsMap;
 
@@ -36,7 +52,7 @@ public class ChimeraManager
   {
     this.structureManager = structureManager;
     chimera = null;
-    chimeraListenerThreads = null;
+    chimeraListenerThread = null;
     currentModelsMap = new HashMap<Integer, ChimeraModel>();
 
   }
@@ -354,7 +370,15 @@ public class ChimeraManager
   {
     chimera = null;
     currentModelsMap.clear();
-    chimeraListenerThreads = null;
+    if (USE_REST)
+    {
+      this.chimeraRestPort = 0;
+    }
+    else
+    {
+      chimeraListenerThread.requestStop();
+      chimeraListenerThread = null;
+    }
     structureManager.clearOnChimeraExit();
   }
 
@@ -525,12 +549,13 @@ public class ChimeraManager
         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)
       {
@@ -541,10 +566,18 @@ 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.chimeraRestPort = getPortNumber();
+        System.out.println("Chimera REST API on port " + chimeraRestPort);
+      }
+      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 +592,42 @@ 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 "
+                + 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
@@ -683,7 +752,8 @@ public class ChimeraManager
    */
   public List<String> sendChimeraCommand(String command, boolean reply)
   {
-    if (!isChimeraLaunched())
+    if (!isChimeraLaunched() || command == null
+            || "".equals(command.trim()))
     {
       return null;
     }
@@ -699,38 +769,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<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.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");
+    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()