1
2
3
4
5
6
7 package org.astrogrid.datacenter.queriers.ogsadai;
8
9 import java.io.File;
10 import java.io.IOException;
11 import org.astrogrid.community.Account;
12 import org.astrogrid.config.SimpleConfig;
13 import org.astrogrid.datacenter.queriers.DatabaseAccessException;
14 import org.astrogrid.datacenter.queriers.DefaultPlugin;
15 import org.astrogrid.datacenter.queriers.Querier;
16 import org.astrogrid.datacenter.queriers.VotableInResults;
17 import org.astrogrid.datacenter.queriers.sql.postgres.PostgresSqlMaker;
18 import org.astrogrid.datacenter.queriers.status.QuerierQuerying;
19 import org.astrogrid.datacenter.query.Query;
20 import org.astrogrid.util.Workspace;
21
22 /***
23 * An AstroGrid datacenter plugin Querier that provides access
24 * to the AstroGrid OGSA-DAI Grid Data Warehouse.
25 *
26 * Due to incompatibilities between vanilla Axis and the customised
27 * version of Axis used by OGSA-DAI, this plugin querier does not talk
28 * directly to the OGSA-DAI installation. (This allows us to insulate
29 * the datacenter runtime from the customised Axis classes).
30 *
31 * Instead, it shells out to the command-line and invokes a
32 * {@link GdsQueryDelegate} (running in a new JVM) to perform
33 * the query. The results supplied by the OGSA-DAI service
34 * in VOTable format.
35 *
36 * Communication between this <code>WarehouseQuerier</code> and
37 * a {@link GdsQueryDelegate} is via process input and output streams.
38 * This version of the WarehouseQuerier requests OGSA-DAI to deliver
39 * its results to a local file. The implications of this are:
40 * <ul>
41 * <li> The tomcat instances hosting the OGSA-DAI service and the
42 * Datacenter service <strong>must be running on the same host</strong>.
43 *
44 * <li>The tomcat instances hosting the OGSA-DAI service and the
45 * Datacenter service should probably be running as the same user
46 * (because the latter must create, and the former must write to,
47 * the same temporary file).
48 * </ul>
49 *
50 * Delivery to a GridFTP URL is also supported by the It5 OGSA-DAI client
51 * code, but requires additional support at the datacenter end in terms
52 * of X.509 authentication etc. It is not explicitly supported in this
53 * Iteration 5 version of the WarehouseQuerier, which relies on
54 * deliver-to-file instead.
55 *
56 * @author K Andrews
57 * @version 1.1
58 * @see GdsQueryDelegate
59 */
60 public class WarehouseQuerier extends DefaultPlugin {
61
62 Workspace workspace = null;
63
64
65 /***
66 * Performs an actual database query (by shelling out to a
67 * GdsQueryDelegate running in a separate JVM).
68 *
69 * Converts input ADQL query into the SQL expected by the
70 * GdsQueryDelegate, using a custom Postgres-flavoured translator.
71 *
72 * @throws IOException, DatabaseAccessException
73 */
74 public void askQuery(Account user, Query query, Querier querier) throws IOException {
75
76 querier.setStatus(new QuerierQuerying(querier.getStatus()));
77
78
79 PostgresSqlMaker sqlMaker = new PostgresSqlMaker();
80 String sql = escapeXmlSpecialChars(sqlMaker.makeSql(query));
81
82 log.debug("Successfully created SQL query from input ADQL");
83
84
85 try {
86 workspace = new Workspace("Warehouse_"+querier.getId());
87 }
88 catch (Exception th) {
89 log.error(th);
90 throw new IOException(
91 "Couldn't create temporary workspace: " +
92 th.getMessage());
93 }
94
95 File tempFile = null;
96
97 if (this.workspace != null) {
98 try {
99 tempFile = workspace.makeWorkFile(TEMP_RESULTS_FILENAME);
100 }
101 catch (Exception e) {
102
103 String errMess = "Couldn't open temporary workspace file: "
104 + e.getMessage();
105 log.error(errMess);
106 throw new DatabaseAccessException(errMess);
107 }
108 }
109 else {
110 log.error("WarehouseQuerier workspace is null");
111 throw new IOException("WarehouseQuerier workspace is null");
112 }
113 doShelledOutQuery(sql, tempFile);
114 VotableInResults results = new VotableInResults(querier, tempFile);
115 results.send(query.getResultsDef(), querier.getUser());
116 workspace.close();
117 }
118
119 /*** Abort - if this is called, try and kill the query and tidy up.
120 * Currently not implemented. I don't know how we're going to
121 * do this using the shelled-out query method, yuk!
122 */
123 public void abort() {
124
125
126 }
127
128 /***
129 * Shells out to the command line to delegate the query operation to
130 * a GdsQueryDelegate running in a separate JVM.
131 *
132 * The GdsQueryDelegate accepts SQL, performs the actual query via
133 * OGSA-DAI, and returns VOTable results.
134 *
135 * @param sql String containing the SQL query to be performed
136 * @param tempFile File to hold the GdsQueryDelegate's VOTable results
137 * @throws DatabaseAccessException
138 */
139 protected void doShelledOutQuery(String sql, File tempFile)
140 throws DatabaseAccessException {
141
142 if (sql == null) {
143 String errMess = "Empty sql query string supplied to WarehouseQuerier";
144 log.error(errMess);
145 throw new DatabaseAccessException(errMess);
146 }
147 if (tempFile == null) {
148 String errMess = "Null results destination supplied to WarehouseQuerier";
149 log.error(errMess);
150 throw new DatabaseAccessException(errMess);
151 }
152
153 log.debug("Commencing doShelledOutQuery");
154
155
156 String[] cmdArgs;
157 if (tempFile == null) {
158 cmdArgs = new String[5];
159 }
160 else {
161 cmdArgs = new String[6];
162 }
163 cmdArgs[0] = getJavaBinary();
164 cmdArgs[1] = "-jar";
165 cmdArgs[2] = getExecutableJar();
166 cmdArgs[3] = sql;
167 cmdArgs[4] = getOgsaDaiRegistryString();
168 cmdArgs[5] = "file://" + tempFile.getAbsolutePath();
169
170
171 log.debug("Command is: " + cmdArgs[0] + " " + cmdArgs[1] +
172 " " + cmdArgs[2] + " " + cmdArgs[3] + " " + cmdArgs[4]);
173
174
175 log.info("Commencing shelled-out query");
176 SystemTalker talker = new SystemTalker();
177 TalkResult result = talker.talk(cmdArgs, "");
178
179 if (result.getErrorCode() != 0) {
180 log.error("Shelled-out query failed:" + result.getStderr());
181 throw new DatabaseAccessException(
182 "External call failed: " + result.getStderr());
183 }
184
185 log.info("Shelled-out query succeeded");
186 }
187
188 /***
189 * Assembles the URL of the OGSA-DAI registry to be used by the
190 * GdsQueryDelegate.
191 *
192 * @return String holding full URL of the OGSA-DAI registry
193 * @throws DatabaseAccessException
194 */
195 protected String getOgsaDaiRegistryString() throws DatabaseAccessException {
196 String host = SimpleConfig.getProperty("WAREHOUSE_OgsaDaiHostString");
197 if (host == null) {
198 String errorMessage =
199 "Fatal error: Property 'WAREHOUSE_OgsaDaiHostString' not found " +
200 " in file 'AstroGridConfig.properties'";
201 log.error(errorMessage);
202 throw new DatabaseAccessException(errorMessage);
203 }
204 String registry = SimpleConfig.getProperty("WAREHOUSE_OgsaDaiRegistryString");
205 if (registry == null) {
206 String errorMessage =
207 "Fatal error: Property 'WAREHOUSE_OgsaDaiRegistryString' not found" +
208 " in file 'WarehouseQuerier.properties'";
209 log.error(errorMessage);
210 throw new DatabaseAccessException(errorMessage);
211 }
212 return host + registry;
213 }
214
215 /***
216 * Assembles the fully-qualified path of the Java JVM to be shelled
217 * out to.
218 *
219 * By default, looks for the environment variable JAVA_HOME and figures
220 * out the path from that.
221 *
222 * If JAVA_HOME is not defined, looks for a local property instead.
223 *
224 * @return String holding full path of the Java JVM binary
225 * @throws DatabaseAccessException
226 */
227 protected String getJavaBinary() throws DatabaseAccessException {
228
229
230 String customJVM = SimpleConfig.getProperty("WAREHOUSE_WarehouseJvm");
231 if (customJVM != null) {
232 return customJVM;
233 }
234 else {
235
236 String javaHome = System.getProperty("java.home");
237 if (javaHome == null) {
238 String errorMessage =
239 "Fatal error: System property 'java.home' not defined! "+
240 "Please set WAREHOUSE_WarehouseJvm property in " +
241 "'AstroGridConfig.properties' file";
242 log.error(errorMessage);
243 throw new DatabaseAccessException(errorMessage);
244 }
245 String separator = System.getProperty("file.separator");
246 if (separator == null) {
247 log.warn("Warning, couldn't get system file.separator, assuming '/'");
248 separator = "/";
249 }
250
251 String fullPath = javaHome + separator + "bin" + separator + "java";
252 File testFile = new File(fullPath);
253 if (!(testFile.exists())) {
254
255 testFile = new File(fullPath + ".exe");
256 if (!(testFile.exists())) {
257 String errorMessage =
258 "Fatal error: Java binary '" + fullPath + "[.exe]' not found! "+
259 "Please set WAREHOUSE_WarehouseJvm property in " +
260 "'AstroGridConfig.properties' file";
261 log.error(errorMessage);
262 throw new DatabaseAccessException(errorMessage);
263 }
264 else {
265 return fullPath + ".exe";
266 }
267 }
268 return fullPath;
269 }
270 }
271
272 /***
273 * Assembles the fully-qualified path of the Java executable jar
274 * containing the OGSA-DAI GdsQueryDelegate.
275 *
276 * The location and name of the jar can be customised for a given
277 * installation in the WarehouseQuerier.properties file.
278 *
279 * @return String holding full path of the GdsQueryDelegate executable jar
280 * @throws DatabaseAccessException
281 */
282 protected String getExecutableJar() throws DatabaseAccessException {
283
284 String jarPath = SimpleConfig.getProperty("WAREHOUSE_ExecutableJarPath");
285 if (jarPath == null) {
286 String errorMessage = "Property 'WAREHOUSE_ExecutableJarPath' not set" +
287 " in properties file 'AstroGridConfig.properties'";
288 log.error(errorMessage);
289 throw new DatabaseAccessException(errorMessage);
290 }
291 String sep = System.getProperty("file.separator");
292 if (sep == null) {
293 log.warn("Warning, couldn't get system file.separator, assuming " +
294 "WAREHOUSE_ExecutableJarPath is properly terminated " +
295 "with file separator");
296 }
297 else {
298 int pathlen = jarPath.length();
299
300 if (!(jarPath.substring(pathlen-1,pathlen).equalsIgnoreCase(sep))) {
301 jarPath = jarPath + sep;
302 }
303 }
304
305 String jarName = SimpleConfig.getProperty("WAREHOUSE_ExecutableJarName");
306 if (jarName == null) {
307 String errorMessage =
308 "Property 'WAREHOUSE_ExecutableJarName' not set in " +
309 "properties file 'AstroGridConfig.properties'";
310 log.error(errorMessage);
311 throw new DatabaseAccessException(errorMessage);
312 }
313 return jarPath + jarName;
314 }
315
316
317 /***
318 * Adjust input string (SQL query) to escape XML special characters
319 * likely to cause problems.
320 * @return Version of string with escaped XML special characters
321 */
322 protected String escapeXmlSpecialChars(String inString) {
323 String outString = "";
324 for (int i = 0; i < inString.length(); i++) {
325 char c = inString.charAt(i);
326 if (c == '<') {
327 outString = outString + "<";
328 }
329 else if (c == '>') {
330 outString = outString + ">";
331 }
332 else if (c == '&') {
333 outString = outString + "&";
334 }
335 else {
336 outString = outString + c;
337 }
338 }
339 return outString;
340 }
341
342 /*** Returns just the number of matches rather than the list of matches */
343 public long getCount(Account user, Query query, Querier querier) throws IOException {
344 throw new UnsupportedOperationException("Not done yet");
345 }
346
347
348
349 private final String DEFAULT_WAREHOUSE_JVM = "/usr/bin/java";
350
351 private final String TEMP_RESULTS_FILENAME = "warehouseResults.xml";
352
353
354 /*** Returns the formats that this plugin can provide. Asks the results class; override in subclasse if nec */
355 public String[] getFormats() {
356 return VotableInResults.getFormats();
357 }
358
359
360
361 }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506