Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / ext / edu / ucsf / rbvi / strucviz2 / ChimeraManager.java
index 6bb3b71..668039b 100644 (file)
  */
 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.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -54,6 +55,7 @@ import org.slf4j.LoggerFactory;
 
 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.
@@ -175,10 +177,11 @@ public class ChimeraManager
     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,
@@ -188,7 +191,8 @@ public class ChimeraManager
             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))
@@ -243,9 +247,8 @@ public class ChimeraManager
       if (!modelList.contains(newModel))
       {
         newModel.setModelName(modelName);
-        sendChimeraCommand(
-                "setattr M name " + modelName + " #"
-                        + newModel.getModelNumber(), false);
+        sendChimeraCommand("setattr M name " + modelName + " #"
+                + newModel.getModelNumber(), false);
         modelList.add(newModel);
       }
     }
@@ -315,8 +318,8 @@ public class ChimeraManager
     {
       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
@@ -329,15 +332,13 @@ public class ChimeraManager
 
   public void startListening()
   {
-    sendChimeraCommand("listen start models; listen start selection", false);
+    sendChimeraCommand("listen start models; listen start selection",
+            false);
   }
 
   public void stopListening()
   {
-    // TODO send this command when viewer connection is closed in Jalview
-    String command = isChimeraX
-            ? "info notify stop models jalview; info notify stop selection jalview"
-            : "listen stop models ; listen stop selection ";
+    String command = "listen stop models ; listen stop selection ";
     sendChimeraCommand(command, false);
   }
 
@@ -351,19 +352,13 @@ public class ChimeraManager
     /*
      * listen for model changes
      */
-    String command = isChimeraX
-            ? ("info notify start models prefix ModelChanged jalview url "
-                    + uri)
-            : ("listen start models url " + uri);
+    String command = "listen start models url " + uri;
     sendChimeraCommand(command, false);
 
     /*
      * listen for selection changes
      */
-    command = isChimeraX
-            ? ("info notify start selection jalview prefix SelectionChanged url "
-                    + uri)
-            : ("listen start select prefix SelectionChanged url " + uri);
+    command = "listen start select prefix SelectionChanged url " + uri;
     sendChimeraCommand(command, false);
   }
 
@@ -439,18 +434,7 @@ public class ChimeraManager
   {
     List<String> selectedResidues = new ArrayList<>();
 
-    /*
-     * skip for now if ChimeraX - request times out
-     */
-    if (isChimeraX)
-    {
-      return selectedResidues;
-    }
-
-    // in fact 'listinfo' (undocumented) works in ChimeraX
-    String command = (isChimeraX
-            ? "info"
-            : "list") + " selection level residue";
+    String command = "list selection level residue";
     List<String> chimeraReply = sendChimeraCommand(command, true);
     if (chimeraReply != null)
     {
@@ -507,7 +491,7 @@ public class ChimeraManager
   {
     List<ChimeraModel> modelList = new ArrayList<>();
     String command = "list models type "
-            + (isChimeraX ? "AtomicStructure" : "molecule");
+            + (isChimeraX() ? "AtomicStructure" : "molecule");
     List<String> list = sendChimeraCommand(command, true);
     if (list != null)
     {
@@ -595,7 +579,6 @@ public class ChimeraManager
       {
         // ensure symbolic links are resolved
         chimeraPath = Paths.get(chimeraPath).toRealPath().toString();
-        isChimeraX = chimeraPath.toLowerCase().contains("chimerax");
         File path = new File(chimeraPath);
         // uncomment the next line to simulate Chimera not installed
         // path = new File(chimeraPath + "x");
@@ -608,16 +591,7 @@ public class ChimeraManager
         args.add(chimeraPath);
         // shows Chimera output window but suppresses REST responses:
         // args.add("--debug");
-        if (isChimeraX())
-        {
-          args.add("--cmd");
-          args.add("remote rest start");
-        }
-        else
-        {
-          args.add("--start");
-          args.add("RESTServer");
-        }
+        addLaunchArguments(args);
         ProcessBuilder pb = new ProcessBuilder(args);
         chimera = pb.start();
         error = "";
@@ -633,8 +607,8 @@ public class ChimeraManager
     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
@@ -649,14 +623,27 @@ public class ChimeraManager
   }
 
   /**
+   * 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
     {
@@ -686,8 +673,8 @@ public class ChimeraManager
       }
     } catch (Exception e)
     {
-      logger.error("Failed to get REST port number from " + responses
-              + ": " + e.getMessage());
+      logger.error("Failed to get REST port number from " + responses + ": "
+              + e.getMessage());
     } finally
     {
       try
@@ -699,11 +686,12 @@ public class ChimeraManager
     }
     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;
   }
 
@@ -760,7 +748,7 @@ public class ChimeraManager
   public List<String> getAttrList()
   {
     List<String> attributes = new ArrayList<>();
-    String command = (isChimeraX ? "info " : "list ") + "resattr";
+    String command = (isChimeraX() ? "info " : "list ") + "resattr";
     final List<String> reply = sendChimeraCommand(command, true);
     if (reply != null)
     {
@@ -789,8 +777,8 @@ public class ChimeraManager
         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)
           {
@@ -820,8 +808,6 @@ public class ChimeraManager
 
   private volatile boolean busy = false;
 
-  private boolean isChimeraX;
-
   /**
    * Send a command to Chimera.
    * 
@@ -835,18 +821,27 @@ public class ChimeraManager
    */
   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)
       {
       }
@@ -868,7 +863,6 @@ public class ChimeraManager
                 + (System.currentTimeMillis() - startTime) + "ms: "
                 + command);
       }
-
     }
   }
 
@@ -882,11 +876,18 @@ public class ChimeraManager
   {
     String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
     List<NameValuePair> commands = new ArrayList<>(1);
-    String method = isChimeraX() ? "GET" : "POST";
+    String method = getHttpRequestMethod();
     if ("GET".equals(method))
     {
-      command = command.replace(" ", "+").replace("#", "%23")
-              .replace("|", "%7C").replace(";", "%3B");
+      try
+      {
+        command = URLEncoder.encode(command, StandardCharsets.UTF_8.name());
+      } catch (UnsupportedEncodingException e)
+      {
+        command = command.replace(" ", "+").replace("#", "%23")
+                .replace("|", "%7C").replace(";", "%3B")
+                .replace(":", "%3A");
+      }
     }
     commands.add(new BasicNameValuePair("command", command));
 
@@ -923,6 +924,17 @@ public class ChimeraManager
   }
 
   /**
+   * 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.
    * 
@@ -973,11 +985,6 @@ public class ChimeraManager
 
   public boolean isChimeraX()
   {
-    return isChimeraX;
-  }
-
-  public void setChimeraX(boolean b)
-  {
-    isChimeraX = b;
+    return false;
   }
 }