View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.gridservice.config;
30  
31  import java.util.Vector;
32  import java.util.Iterator;
33  import java.io.StringReader;
34  
35  import org.dom4j.Node;
36  import org.dom4j.io.SAXReader;
37  import org.dom4j.Document;
38  import org.dom4j.DocumentException;
39  
40  import com.martiansoftware.jsap.JSAP;
41  import com.martiansoftware.jsap.JSAPException;
42  import com.martiansoftware.jsap.Parameter;
43  import com.martiansoftware.jsap.UnflaggedOption;
44  
45  import uk.ac.rdg.resc.jstyx.StyxUtils;
46  import uk.ac.rdg.resc.jstyx.gridservice.server.*;
47  
48  /***
49   * Class containing configuration info for a single Styx Grid Service
50   *
51   * @author Jon Blower
52   * $Revision: 569 $
53   * $Date: 2006-01-05 16:06:35 +0000 (Thu, 05 Jan 2006) $
54   * $Log$
55   * Revision 1.7  2006/01/05 16:06:35  jonblower
56   * SGS clients now deal with possibility that client could be created on a different server
57   *
58   * Revision 1.6  2006/01/05 12:09:15  jonblower
59   * Restructured configuration to give default values for server settings
60   *
61   * Revision 1.5  2005/12/01 08:29:47  jonblower
62   * Refactored XML config handling to simplify clients
63   *
64   * Revision 1.4  2005/11/10 19:50:43  jonblower
65   * Added code to handle output files
66   *
67   * Revision 1.3  2005/11/10 08:57:21  jonblower
68   * Added code to handle output files and streams
69   *
70   * Revision 1.2  2005/11/09 17:45:00  jonblower
71   * Changes to storing of XML config information
72   *
73   * Revision 1.1  2005/11/07 20:59:34  jonblower
74   * Refactored SGS config classes to new package
75   *
76   * Revision 1.20  2005/11/04 19:28:20  jonblower
77   * Changed structure of input files in config file and Styx namespace
78   *
79   * Revision 1.19  2005/11/03 07:42:47  jonblower
80   * Implemented JSAP-based parameter parsing
81   *
82   * Revision 1.14  2005/10/18 14:08:14  jonblower
83   * Removed inputfiles from namespace
84   *
85   * Revision 1.12  2005/08/02 08:05:18  jonblower
86   * Continuing to implement steering
87   *
88   * Revision 1.10  2005/08/01 16:38:05  jonblower
89   * Implemented simple parameter handling
90   *
91   * Revision 1.9  2005/06/14 07:45:16  jonblower
92   * Implemented setting of params and async notification of parameter changes
93   *
94   * Revision 1.8  2005/05/19 18:42:07  jonblower
95   * Implementing specification of input files required by SGS
96   *
97   * Revision 1.7  2005/05/16 11:00:53  jonblower
98   * Changed SGS config XML file structure: separated input and output streams and changed some tag names
99   *
100  * Revision 1.6  2005/05/13 16:49:34  jonblower
101  * Coded dynamic detection and display of service data, also included streams in config file
102  *
103  * Revision 1.5  2005/05/11 15:14:30  jonblower
104  * Implemented more flexible definition of service data elements
105  *
106  * Revision 1.4  2005/05/11 13:45:19  jonblower
107  * Converted SGS config code to use dom4j and Jaxen for XML parsing
108  *
109  * Revision 1.3  2005/04/27 16:11:35  jonblower
110  * Added capability to add documentation files to SGS namespace
111  *
112  * Revision 1.2  2005/03/26 14:27:53  jonblower
113  * Modified to use SGSConfigException
114  *
115  * Revision 1.1  2005/03/24 17:34:58  jonblower
116  * Initial import
117  *
118  */
119 public class SGSConfig
120 {
121     private SGSServerConfig serverConfig; // Configuration of the server (port, SSL details etc)
122     private Node rootNode;      // The Node in the XML config file that is at the
123                                 // root of this Styx Grid Service
124     private String name;        // The name of this SGS
125     private String command;     // The command that is run by this SGS
126     private String workDir;     // The working directory of this SGS
127     private String description; // Short description of this SGS
128     private String configXMLForClient;   // XML snippet used to create this config in a form suitable for clients
129     
130     private Vector inputs;      // The inputs (files and streams) expected by this service
131                                 // Instance of SGSInputFile
132     private Vector outputs;     // The outputs (files and streams) made available by this service
133     private Vector docFiles;    // The documentation files
134     private Vector params;      // The parameters for this service
135     private JSAP paramParser;   // Object that parses the parameters for this service
136     private Vector steerables;  // The steerable parameters for this SGS
137     private Vector serviceData; // The service data elements for this SGS
138 
139     /***
140      * This is called by the SGS server program to generate a configuration
141      * object from the XML config file.
142      * @param gridService The Node in the XML config file that is at the
143      * root of this Styx Grid Service
144      * @param serverConfig Configuration of the server
145      * @throws IllegalArgumentException if the name of the SGS contains
146      * a space.
147      */
148     public SGSConfig(Node gridService, SGSServerConfig serverConfig)
149         throws SGSConfigException
150     {
151         this.init(gridService);
152         this.serverConfig = serverConfig;
153         this.rootNode = gridService;
154         this.workDir = this.serverConfig.getCacheLocation() +
155             StyxUtils.SYSTEM_FILE_SEPARATOR + name;
156         this.setConfigXMLForClient();
157         
158         // Create the documentation files
159         this.docFiles = new Vector();
160         Iterator docListIter = gridService.selectNodes("docs/doc").iterator();
161         while(docListIter.hasNext())
162         {
163             Node docEl = (Node)docListIter.next();
164             String name = docEl.valueOf("@name");
165             String location = docEl.valueOf("@location");
166             this.docFiles.add(new DocFile(name, location));
167         }
168     }
169     
170     /***
171      * This constructor is called by client programs to create an SGSConfig
172      * object from an XML snippet that has been read over the Styx interface.
173      * This does not populate any of the server-specific fields of this class
174      * (e.g. the working directory).  After calling this constructor, clients 
175      * will have access to all the fields that relate to <b>instances</b> of 
176      * this SGS.
177      * @param configXML XML snippet representing the Styx Grid Service
178      */
179     public SGSConfig(String configXML) throws SGSConfigException
180     {
181         // Parse the xml document using dom4j (without validation, since we
182         // don't have the DTD.  This is OK because the server should have validated
183         // the XML anyway)
184         SAXReader reader = new SAXReader(false);
185         try
186         {
187             Document doc = reader.read(new StringReader(configXML));
188             this.init(doc.getRootElement());
189         }
190         catch(DocumentException de)
191         {
192             // TODO: log full stack trace
193             throw new SGSConfigException("Error parsing config XML: " + de.getMessage());
194         }
195     }
196     
197     /***
198      * Initializes class variables from the given Node in an XML document
199      */
200     private void init(Node gridService) throws SGSConfigException
201     {
202         this.name = gridService.valueOf("@name");
203         // Check that the name is valid
204         if (this.name.indexOf(" ") != -1)
205         {
206             // TODO: check for other whitespace characters
207             throw new IllegalArgumentException("The name of an SGS cannot contain a space");
208         }
209         this.command = gridService.valueOf("@command");
210         this.description = gridService.valueOf("@description");
211         boolean usingStdin = false;
212 
213         // Create the parameters
214         this.params = new Vector();
215         this.paramParser = new JSAP();
216         Iterator paramListIter = gridService.selectNodes("params/param").iterator();
217         while(paramListIter.hasNext())
218         {
219             Node paramEl = (Node)paramListIter.next();
220             SGSParam param = new SGSParam(paramEl);
221             this.params.add(param);
222             try
223             {
224                 this.paramParser.registerParameter(param.getParameter());
225             }
226             catch (JSAPException jsape)
227             {
228                 throw new SGSConfigException("Error parsing parameters: " + jsape.getMessage());
229             }
230         }
231         
232         // Look for input files and streams
233         this.inputs = new Vector();
234         Iterator inputListIter = gridService.selectNodes("inputs/input").iterator();
235         while(inputListIter.hasNext())
236         {
237             Node input = (Node)inputListIter.next();
238             SGSInput sgsIn = new SGSInput(input.valueOf("@type"), input.valueOf("@name"));
239             if (sgsIn.getType() == SGSInput.STREAM)
240             {
241                 usingStdin = true;
242             }
243             else if (sgsIn.getType() == SGSInput.FILE_FROM_PARAM)
244             {
245                 // This input file is linked to a parameter.  We need to find
246                 // the parameter in question
247                 boolean found = false;
248                 for (int i = 0; i < this.params.size() && !found; i++)
249                 {
250                     SGSParam param = (SGSParam)this.params.get(i);
251                     if (param.getName().equals(sgsIn.getName()))
252                     {
253                         found = true;
254                         param.setInputFile(sgsIn);
255                     }
256                 }
257                 if (!found)
258                 {
259                     throw new SGSConfigException("Error setting input files:" +
260                         " parameter " + sgsIn.getName() + " does not exist");
261                 }
262             }
263             this.inputs.add(sgsIn);
264         }
265         
266         // Now the output files and streams
267         this.outputs = new Vector();
268         Iterator outputListIter = gridService.selectNodes("outputs/output").iterator();
269         while(outputListIter.hasNext())
270         {
271             Node output = (Node)outputListIter.next();
272             SGSOutput sgsOut = new SGSOutput(output.valueOf("@type"), output.valueOf("@name"));
273             if (sgsOut.getType() == SGSOutput.FILE_FROM_PARAM)
274             {
275                 // This output file is linked to a parameter.  We need to find
276                 // the parameter in question
277                 boolean found = false;
278                 for (int i = 0; i < this.params.size() && !found; i++)
279                 {
280                     SGSParam param = (SGSParam)this.params.get(i);
281                     if (param.getName().equals(sgsOut.getName()))
282                     {
283                         found = true;
284                         // Output files can't be set by a greedy parameter: the
285                         // parameter must only consume one command-line argument
286                         if (param.getParameter() instanceof UnflaggedOption)
287                         {
288                             UnflaggedOption uo = (UnflaggedOption)param.getParameter();
289                             if (uo.isGreedy())
290                             {
291                                 throw new SGSConfigException("Cannot link an" +
292                                     " output file to a greedy parameter");
293                             }
294                         }
295                         param.setOutputFile(sgsOut);
296                     }
297                 }
298                 if (!found)
299                 {
300                     throw new SGSConfigException("Error setting output files:" +
301                         " parameter " + sgsOut.getName() + " does not exist");
302                 }
303             }
304             this.outputs.add(sgsOut);
305         }
306         
307         // Now the steerable parameters
308         this.steerables = new Vector();
309         Iterator steerableListIter = gridService.selectNodes("steering/steerable").iterator();
310         while(steerableListIter.hasNext())
311         {
312             Node steerableEl = (Node)steerableListIter.next();
313             this.steerables.add(new Steerable(steerableEl));
314         }
315         
316         // Create the service data elements
317         this.serviceData = new Vector();
318         Iterator serviceDataIter =
319             gridService.selectNodes("serviceData/serviceDataElement").iterator();
320         while(serviceDataIter.hasNext())
321         {
322             Node sdEl = (Node)serviceDataIter.next();
323             SDEConfig sdeConf = new SDEConfig(sdEl);
324             // If we've asked for bytesConsumed to be available we must be
325             // reading from stdin
326             if (sdeConf.getName().equals("bytesConsumed") && !usingStdin)
327             {
328                 throw new SGSConfigException("The bytesConsumed service data" +
329                     " element is only available when stdin is also available.");
330             }
331             this.serviceData.add(sdeConf);
332         }
333     }
334     
335     /***
336      * @return the configuration (port, SSL details etc) of the server
337      */
338     public SGSServerConfig getServerConfig()
339     {
340         return this.serverConfig;
341     }
342 
343     /***
344      * @return the name of the service
345      */
346     public String getName()
347     {
348         return this.name;
349     }
350 
351     /***
352      * @return the command string that is run when the SGS is started.  This
353      * is the string that is passed to Runtime.exec().  This
354      * is only meaningful for server-side code (if a client calls this method it
355      * will return null).
356      */ 
357     public String getCommand()
358     {
359         return this.command;
360     }
361 
362     /***
363      * @return a String that briefly describes this SGS.
364      */ 
365     public String getDescription()
366     {
367         return this.description;
368     }
369 
370     /***
371      * @return the working directory of this SGS. Each instance of the SGS
372      * will use a subdirectory of this directory as its working directory.  This
373      * is only meaningful for server-side code (if a client calls this method it
374      * will return null).
375      */
376     public String getWorkingDirectory()
377     {
378         return this.workDir;
379     }
380     
381     /***
382      * Sets the XML that was used to create this config object in a form
383      * suitable for clients to read.  This is the same as the XML that was used
384      * to create the config object except that the command attribute of the root
385      * element and the documentation section are missing.
386      */
387     private void setConfigXMLForClient()
388     {
389         // We will have to construct some of the XML by hand
390         StringBuffer buf = new StringBuffer("<gridservice name=\"");
391         buf.append(this.name);
392         buf.append("\" description=\"");
393         buf.append(this.description);
394         buf.append("\">");
395         
396         // Loop over all the child elements and add to the XML unless the child
397         // element is the documentation element
398         Iterator it = this.rootNode.selectNodes("*").iterator();
399         while (it.hasNext())
400         {
401             Node node = (Node)it.next();
402             if (!node.getName().equals("docs"))
403             {
404                 buf.append(node.asXML());
405             }
406         }
407             
408         buf.append("</gridservice>");
409         this.configXMLForClient = buf.toString();
410     }
411     
412     /***
413      * @return the XML that was used to create this config object in a form
414      * suitable for clients to read.  This is the same as the XML that was used
415      * to create the config object except that the command attribute of the root
416      * element and the documentation section are missing.
417      */
418     public String getConfigXMLForClient()
419     {
420         return this.configXMLForClient;
421     }
422     
423     /***
424      * @return Vector of SGSInput objects containing details of all the 
425      * input files and streams expected by the service
426      */
427     public Vector getInputs()
428     {
429         return this.inputs;
430     }
431     
432     /***
433      * @return Vector of SGSOutput objects containing details of all the 
434      * output files and streams exposed by the service
435      */
436     public Vector getOutputs()
437     {
438         return this.outputs;
439     }
440 
441     /*** 
442      * @return JSAP object that is used to parse the command-line parameters
443      */
444     public JSAP getParamParser()
445     {
446         return this.paramParser;
447     }
448 
449     /*** 
450      * @return Vector containing details of all the parameters (as SGSParam objects)
451      */
452     public Vector getParams()
453     {
454         return this.params;
455     }
456     
457     /***
458      * @return Vector of Steerable objects containing details of the parameters
459      * that can be adjusted as the executable is running
460      */
461     public Vector getSteerables()
462     {
463         return this.steerables;
464     }
465 
466     /*** 
467      * @return Vector of documentation file objects
468      */
469     public Vector getDocFiles()
470     {
471         return this.docFiles;
472     }
473 
474     /*** 
475      * @return Vector of service data objects
476      */
477     public Vector getServiceData()
478     {
479         return this.serviceData;
480     }
481 }