1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package ubic.basecode.util.r;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.net.URL;
24 import java.util.Iterator;
25 import java.util.List;
26
27 import org.apache.commons.configuration2.PropertiesConfiguration;
28 import org.apache.commons.configuration2.ex.ConfigurationException;
29 import org.apache.commons.configuration2.io.FileHandler;
30 import org.apache.commons.lang3.StringUtils;
31 import org.rosuda.REngine.REXP;
32 import org.rosuda.REngine.REXPMismatchException;
33 import org.rosuda.REngine.REngineException;
34 import org.rosuda.REngine.RList;
35 import org.rosuda.REngine.Rserve.RConnection;
36 import org.rosuda.REngine.Rserve.RserveException;
37
38 import ubic.basecode.dataStructure.matrix.DenseDoubleMatrix;
39 import ubic.basecode.dataStructure.matrix.DoubleMatrix;
40 import ubic.basecode.util.ConfigUtils;
41
42
43
44
45
46 public class RServeClient extends AbstractRClient {
47
48
49
50
51 private static final int DEFAULT_PORT = 6311;
52
53 private static final int MAX_CONNECT_TRIES = 10;
54
55 private static final int MAX_EVAL_TRIES = 3;
56
57 private final static String os = System.getProperty( "os.name" ).toLowerCase();
58
59
60
61
62
63 protected static String findRserveCommand() throws ConfigurationException {
64 URL userSpecificConfigFileLocation = ConfigUtils.locate( "local.properties" );
65
66 PropertiesConfiguration userConfig = null;
67 if ( userSpecificConfigFileLocation != null ) {
68 userConfig = new PropertiesConfiguration();
69 FileHandler handler = new FileHandler( userConfig );
70 handler.setFileName( "local.properties" );
71 handler.load();
72 }
73 String rserveExecutable = null;
74 if ( userConfig != null ) {
75 rserveExecutable = userConfig.getString( "rserve.start.command" );
76 }
77 if ( StringUtils.isBlank( rserveExecutable ) ) {
78 log.info( "Rserve command not configured? Trying fallbacks" );
79 if ( os.startsWith( "windows" ) ) {
80 rserveExecutable = System.getenv( "R_HOME" ) + File.separator + "library" + File.separator + "Rserve"
81 + File.separator + "Rserve.exe";
82 } else {
83 rserveExecutable = "R CMD Rserve";
84 }
85 }
86 return rserveExecutable;
87 }
88
89 private RConnection connection = null;
90
91
92
93
94
95
96 protected RServeClient() throws IOException {
97 if ( !connect() ) {
98 throw new IOException( "Could not connect to Rserve" );
99 }
100 }
101
102
103
104
105
106 protected RServeClient( String host ) throws IOException {
107 if ( !connect( host, DEFAULT_PORT ) ) {
108 throw new IOException( "Could not connect to Rserve" );
109 }
110 }
111
112
113
114
115
116
117 @Override
118 public void assign( String argName, double[] arg ) {
119 checkConnection();
120
121 try {
122 connection.assign( argName, arg );
123 } catch ( REngineException e ) {
124 throw new RuntimeException( e );
125 }
126
127 }
128
129
130
131
132
133
134 @Override
135 public void assign( String arg0, int[] arg1 ) {
136 if ( StringUtils.isBlank( arg0 ) ) {
137 throw new IllegalArgumentException( "Must supply valid variable name" );
138 }
139 checkConnection();
140 try {
141 connection.assign( arg0, arg1 );
142 } catch ( REngineException e ) {
143 throw new RuntimeException( e );
144 }
145 }
146
147
148
149
150
151
152
153
154
155
156
157 @Override
158 public void assign( String sym, String ct ) {
159 if ( StringUtils.isBlank( sym ) ) {
160 throw new IllegalArgumentException( "Must supply valid variable name" );
161 }
162 try {
163 this.checkConnection();
164 connection.assign( sym, ct );
165 } catch ( RserveException e ) {
166 throw new RuntimeException( "Assignment failed: " + sym + " value " + ct, e );
167 }
168 }
169
170
171
172
173
174
175 @Override
176 public void assign( String argName, String[] array ) {
177 if ( array == null || array.length == 0 ) {
178 throw new IllegalArgumentException( "Array must not be null or empty" );
179 }
180 if ( StringUtils.isBlank( argName ) ) {
181 throw new IllegalArgumentException( "Must supply valid variable name" );
182 }
183 try {
184 log.debug( "assign: " + argName + "<-" + array.length + " strings." );
185 this.checkConnection();
186 connection.assign( argName, array );
187 } catch ( REngineException e ) {
188 throw new RuntimeException( "Failure with assignment: " + argName + "<-" + array.length + " strings." + e );
189 }
190 }
191
192
193
194
195
196 public boolean connect() {
197 return connect( true );
198 }
199
200 @Override
201 public void disconnect() {
202 if ( connection != null && connection.isConnected() ) connection.close();
203 connection = null;
204 }
205
206
207
208
209
210
211 @Override
212 public REXP eval( String command ) {
213 log.debug( "eval: " + command );
214
215 int lockValue = 0;
216 try {
217
218
219
220
221 for ( int i = 0; i < MAX_EVAL_TRIES + 1; i++ ) {
222 RuntimeException ex = null;
223 try {
224 checkConnection();
225 lockValue = connection.lock();
226 REXP r = connection.eval( "try(" + command + ", silent=T)" );
227 if ( r == null ) return null;
228
229 if ( r.inherits( "try-error" ) ) {
230
231
232
233 throw new RuntimeException( "Error from R: " + r.asString() );
234 }
235 return r;
236
237 } catch ( RserveException e ) {
238 ex = new RuntimeException( "Error excecuting " + command + ": " + e.getMessage(), e );
239 } catch ( REXPMismatchException e ) {
240 throw new RuntimeException( "Error processing apparent error object returned by " + command + ": "
241 + e.getMessage(), e );
242 }
243
244 if ( i == MAX_EVAL_TRIES ) {
245 throw ex;
246 }
247
248 try {
249 log.debug( "Eval failed, retrying" );
250 Thread.sleep( 200 );
251 } catch ( InterruptedException e ) {
252 return null;
253 }
254
255 }
256
257 throw new RuntimeException( "Evaluation failed! No details available" );
258 } finally {
259 if ( lockValue != 0 ) connection.unlock( lockValue );
260 }
261 }
262
263
264
265
266
267
268 @Override
269 public void finalize() {
270 this.disconnect();
271 }
272
273
274
275
276
277
278 @Override
279 public String getLastError() {
280 return connection.getLastError();
281 }
282
283
284
285
286 @Override
287 public boolean isConnected() {
288 if ( connection != null && connection.isConnected() ) return true;
289 return false;
290 }
291
292
293
294
295
296
297 @Override
298 public DoubleMatrix<String, String> retrieveMatrix( String variableName ) {
299 try {
300 log.debug( "Retrieving " + variableName );
301
302
303
304
305
306 REXP r = this.eval( "data.frame(t(" + variableName + "))" );
307 if ( r == null ) throw new IllegalArgumentException( variableName + " not found in R context" );
308
309 RList dataframe = r.asList();
310 int numrows = dataframe.size();
311 double[][] results = new double[numrows][];
312 int i = 0;
313 for ( Iterator<?> it = dataframe.iterator(); it.hasNext(); ) {
314 REXP next = ( REXP ) it.next();
315 double[] row = next.asDoubles();
316 results[i] = row;
317 i++;
318 }
319
320 DoubleMatrix<String, String> resultObject = new DenseDoubleMatrix<String, String>( results );
321
322 retrieveRowAndColumnNames( variableName, resultObject );
323 return resultObject;
324 } catch ( REXPMismatchException e ) {
325 throw new RuntimeException( "Failed to get back matrix for variable " + variableName, e );
326 }
327
328 }
329
330
331
332
333
334
335 @Override
336 public void voidEval( String command ) {
337 if ( command == null ) throw new IllegalArgumentException( "Null command" );
338 this.checkConnection();
339
340 log.debug( "voidEval: " + command );
341 eval( command );
342
343 }
344
345
346
347
348 private void checkConnection() {
349 if ( !this.isConnected() ) {
350
351
352
353
354
355 log.warn( "Not connected, trying to reconnect" );
356 boolean ok = false;
357 for ( int i = 0; i < MAX_CONNECT_TRIES; i++ ) {
358 try {
359 Thread.sleep( 200 );
360 } catch ( InterruptedException e ) {
361 return;
362 }
363 ok = this.connect();
364 if ( ok ) break;
365 }
366 if ( !ok ) {
367 throw new RuntimeException( "Not connected" );
368 }
369 }
370 }
371
372
373
374
375 private boolean connect( boolean beQuiet ) {
376 if ( connection != null && connection.isConnected() ) {
377 return true;
378 }
379 int tries = 3;
380 Exception ex = null;
381 for ( int i = 0; i < tries; i++ ) {
382 try {
383 connection = new RConnection();
384 return true;
385 } catch ( RserveException e ) {
386 ex = e;
387 try {
388 Thread.sleep( 100 );
389 } catch ( InterruptedException e1 ) {
390 return false;
391 }
392 }
393 }
394 if ( !beQuiet ) {
395 log.error( "Could not connect to RServe: " + ( ex == null ? "" : ex.getMessage() ) );
396 }
397 return false;
398 }
399
400
401
402
403
404
405 private boolean connect( String host, int port ) {
406 if ( connection != null && connection.isConnected() ) {
407 return true;
408 }
409 try {
410 connection = new RConnection( host, port );
411 } catch ( RserveException e ) {
412 log.error( "Could not connect to RServe: " + e.getMessage() );
413 return false;
414 }
415 log.info( "Connected via RServe." );
416 return true;
417 }
418
419
420
421
422
423
424 private void retrieveRowAndColumnNames( String variableName, DoubleMatrix<String, String> resultObject ) {
425 List<String> rowNames = this.stringListEval( "dimnames(" + variableName + ")[1][[1]]" );
426
427 if ( rowNames.size() == resultObject.rows() ) {
428 resultObject.setRowNames( rowNames );
429 }
430
431 List<String> colNames = this.stringListEval( "dimnames(" + variableName + ")[2][[1]]" );
432
433 if ( colNames.size() == resultObject.columns() ) {
434 resultObject.setColumnNames( colNames );
435 }
436 }
437
438 }