Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.ext.pymol

File PymolManager.java

 

Coverage histogram

../../../img/srcFileCovDistChart2.png
50% of files have more coverage

Code metrics

42
109
7
1
317
238
34
0.31
15.57
7
4.86

Classes

Class Line # Actions
PymolManager 21 109 34
0.1962025319.6%
 

Contributing tests

This file is covered by 2 tests. .

Source view

1    package jalview.ext.pymol;
2   
3    import java.io.BufferedReader;
4    import java.io.File;
5    import java.io.IOException;
6    import java.io.InputStream;
7    import java.io.InputStreamReader;
8    import java.io.PrintWriter;
9    import java.net.HttpURLConnection;
10    import java.net.SocketException;
11    import java.net.URL;
12    import java.nio.file.Paths;
13    import java.util.ArrayList;
14    import java.util.List;
15   
16    import jalview.bin.Cache;
17    import jalview.gui.Preferences;
18    import jalview.structure.StructureCommand;
19    import jalview.structure.StructureCommandI;
20   
 
21    public class PymolManager
22    {
23    private static final int RPC_REPLY_TIMEOUT_MS = 15000;
24   
25    private static final int CONNECTION_TIMEOUT_MS = 100;
26   
27    private static final String POST1 = "<methodCall><methodName>";
28   
29    private static final String POST2 = "</methodName><params>";
30   
31    private static final String POST3 = "</params></methodCall>";
32   
33    private Process pymolProcess;
34   
35    private int pymolXmlRpcPort;
36   
37    /**
38    * Returns a list of paths to try for the PyMOL executable. Any user
39    * preference is placed first, otherwise 'standard' paths depending on the
40    * operating system.
41    *
42    * @return
43    */
 
44  0 toggle public static List<String> getPymolPaths()
45    {
46  0 return getPymolPaths(System.getProperty("os.name"));
47    }
48   
49    /**
50    * Returns a list of paths to try for the PyMOL executable. Any user
51    * preference is placed first, otherwise 'standard' paths depending on the
52    * operating system.
53    *
54    * @param os
55    * operating system as reported by environment variable
56    * {@code os.name}
57    * @return
58    */
 
59  4 toggle protected static List<String> getPymolPaths(String os)
60    {
61  4 List<String> pathList = new ArrayList<>();
62   
63  4 String userPath = Cache
64    .getDefault(Preferences.PYMOL_PATH, null);
65  4 if (userPath != null)
66    {
67  0 pathList.add(userPath);
68    }
69   
70    /*
71    * add default installation paths
72    */
73  4 String pymol = "PyMOL";
74  4 if (os.startsWith("Linux"))
75    {
76  1 pathList.add("/usr/local/pymol/bin/" + pymol);
77  1 pathList.add("/usr/local/bin/" + pymol);
78  1 pathList.add("/usr/bin/" + pymol);
79  1 pathList.add(System.getProperty("user.home") + "/opt/bin/" + pymol);
80    }
81  3 else if (os.startsWith("Windows"))
82    {
83    // todo Windows installation path(s)
84    }
85  2 else if (os.startsWith("Mac"))
86    {
87  1 pathList.add("/Applications/PyMOL.app/Contents/MacOS/" + pymol);
88    }
89  4 return pathList;
90    }
91   
 
92  0 toggle public boolean isPymolLaunched()
93    {
94    // TODO pull up generic methods for external viewer processes
95  0 boolean launched = false;
96  0 if (pymolProcess != null)
97    {
98  0 try
99    {
100  0 pymolProcess.exitValue();
101    // if we get here, process has ended
102    } catch (IllegalThreadStateException e)
103    {
104    // ok - not yet terminated
105  0 launched = true;
106    }
107    }
108  0 return launched;
109    }
110   
111    /**
112    * Sends the command to Pymol; if requested, tries to get and return any
113    * replies, else returns null
114    *
115    * @param command
116    * @param getReply
117    * @return
118    */
 
119  0 toggle public List<String> sendCommand(StructureCommandI command,
120    boolean getReply)
121    {
122  0 String postBody = getPostRequest(command);
123    // System.out.println(postBody);// debug
124  0 String rpcUrl = "http://127.0.0.1:" + this.pymolXmlRpcPort;
125  0 PrintWriter out = null;
126  0 BufferedReader in = null;
127  0 List<String> result = getReply ? new ArrayList<>() : null;
128   
129  0 try
130    {
131  0 URL realUrl = new URL(rpcUrl);
132  0 HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
133  0 conn.setRequestProperty("accept", "*/*");
134  0 conn.setRequestProperty("content-type", "text/xml");
135  0 conn.setDoOutput(true);
136  0 conn.setDoInput(true);
137  0 out = new PrintWriter(conn.getOutputStream());
138  0 out.print(postBody);
139  0 out.flush();
140  0 int rc = conn.getResponseCode();
141  0 if (rc != HttpURLConnection.HTTP_OK)
142    {
143  0 Cache.log.error(
144    String.format("Error status from %s: %d", rpcUrl, rc));
145  0 return result;
146    }
147   
148  0 InputStream inputStream = conn.getInputStream();
149  0 if (getReply)
150    {
151  0 in = new BufferedReader(new InputStreamReader(inputStream));
152  0 String line;
153  0 while ((line = in.readLine()) != null)
154    {
155  0 result.add(line);
156    }
157    }
158    } catch (SocketException e)
159    {
160    // thrown when 'quit' command is sent to PyMol
161  0 Cache.log.warn(String.format("Request to %s returned %s", rpcUrl,
162    e.toString()));
163    } catch (Exception e)
164    {
165  0 e.printStackTrace();
166    } finally
167    {
168  0 if (out != null)
169    {
170  0 out.close();
171    }
172    }
173  0 return result;
174    }
175   
176    /**
177    * Builds the body of the XML-RPC format POST request to execute the command
178    *
179    * @param command
180    * @return
181    */
 
182  2 toggle static String getPostRequest(StructureCommandI command)
183    {
184  2 StringBuilder sb = new StringBuilder(64);
185  2 sb.append(POST1).append(command.getCommand()).append(POST2);
186  2 if (command.hasParameters())
187    {
188  1 for (String p : command.getParameters())
189    {
190    /*
191    * for now assuming all are string - <string> element is optional
192    * refactor in future if other data types needed
193    * https://www.tutorialspoint.com/xml-rpc/xml_rpc_data_model.htm
194    */
195  2 sb.append("<parameter><value>").append(p)
196    .append("</value></parameter>");
197    }
198    }
199  2 sb.append(POST3);
200  2 return sb.toString();
201    }
202   
 
203  0 toggle public Process launchPymol()
204    {
205    // todo pull up much of this
206    // Do nothing if already launched
207  0 if (isPymolLaunched())
208    {
209  0 return pymolProcess;
210    }
211   
212  0 String error = "Error message: ";
213  0 for (String pymolPath : getPymolPaths())
214    {
215  0 try
216    {
217    // ensure symbolic links are resolved
218  0 pymolPath = Paths.get(pymolPath).toRealPath().toString();
219  0 File path = new File(pymolPath);
220    // uncomment the next line to simulate Pymol not installed
221    // path = new File(pymolPath + "x");
222  0 if (!path.canExecute())
223    {
224  0 error += "File '" + path + "' does not exist.\n";
225  0 continue;
226    }
227  0 List<String> args = new ArrayList<>();
228  0 args.add(pymolPath);
229  0 args.add("-R"); // https://pymolwiki.org/index.php/RPC
230  0 ProcessBuilder pb = new ProcessBuilder(args);
231  0 pymolProcess = pb.start();
232  0 error = "";
233  0 break;
234    } catch (Exception e)
235    {
236    // Pymol could not be started using this path
237  0 error += e.getMessage();
238    }
239    }
240   
241  0 if (pymolProcess != null)
242    {
243  0 this.pymolXmlRpcPort = getPortNumber();
244  0 if (pymolXmlRpcPort > 0)
245    {
246  0 Cache.log.info("PyMOL XMLRPC started on port " + pymolXmlRpcPort);
247    }
248    else
249    {
250  0 error += "Failed to read PyMOL XMLRPC port number";
251  0 Cache.log.error(error);
252  0 pymolProcess.destroy();
253  0 pymolProcess = null;
254    }
255    }
256   
257  0 return pymolProcess;
258    }
259   
 
260  0 toggle private int getPortNumber()
261    {
262    // TODO pull up most of this!
263  0 int port = 0;
264  0 InputStream readChan = pymolProcess.getInputStream();
265  0 BufferedReader lineReader = new BufferedReader(
266    new InputStreamReader(readChan));
267  0 StringBuilder responses = new StringBuilder();
268  0 try
269    {
270  0 String response = lineReader.readLine();
271  0 while (response != null)
272    {
273  0 responses.append("\n" + response);
274    // expect: xml-rpc server running on host localhost, port 9123
275  0 if (response.contains("xml-rpc"))
276    {
277  0 String[] tokens = response.split(" ");
278  0 for (int i = 0; i < tokens.length - 1; i++)
279    {
280  0 if ("port".equals(tokens[i]))
281    {
282  0 port = Integer.parseInt(tokens[i + 1]);
283  0 break;
284    }
285    }
286    }
287  0 if (port > 0)
288    {
289  0 break; // hack for hanging readLine()
290    }
291  0 response = lineReader.readLine();
292    }
293    } catch (Exception e)
294    {
295  0 Cache.log.error("Failed to get REST port number from " + responses
296    + ": " + e.getMessage());
297    // logger.error("Failed to get REST port number from " + responses + ": "
298    // + e.getMessage());
299    } finally
300    {
301  0 try
302    {
303  0 lineReader.close();
304    } catch (IOException e2)
305    {
306    }
307    }
308  0 if (port == 0)
309    {
310  0 Cache.log.error("Failed to start PyMOL with XMLRPC, response was: "
311    + responses);
312    }
313  0 Cache.log.error("PyMOL started with XMLRPC on port " + port);
314  0 return port;
315    }
316   
317    }