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.server;
30  
31  import java.io.File;
32  import java.net.URL;
33  import java.net.MalformedURLException;
34  
35  import org.apache.mina.common.ByteBuffer;
36  
37  import com.martiansoftware.jsap.JSAP;
38  import com.martiansoftware.jsap.Parameter;
39  import com.martiansoftware.jsap.Switch;
40  import com.martiansoftware.jsap.Option;
41  import com.martiansoftware.jsap.FlaggedOption;
42  import com.martiansoftware.jsap.UnflaggedOption;
43  
44  import uk.ac.rdg.resc.jstyx.gridservice.config.SGSParam;
45  
46  import uk.ac.rdg.resc.jstyx.server.AsyncStyxFile;
47  import uk.ac.rdg.resc.jstyx.server.InMemoryFile;
48  import uk.ac.rdg.resc.jstyx.server.StyxFileClient;
49  import uk.ac.rdg.resc.jstyx.StyxException;
50  import uk.ac.rdg.resc.jstyx.StyxUtils;
51  
52  /***
53   * A StyxFile interface to a parameter that is passed to an SGS instance as 
54   * part of the command line of the underlying executable.  This is an AsyncStyxFile
55   * so clients can be automatically notified of changes to the value of the
56   * parameter.
57   *
58   * @author Jon Blower
59   * $Revision: 609 $
60   * $Date: 2006-03-31 18:09:42 +0100 (Fri, 31 Mar 2006) $
61   * $Log$
62   * Revision 1.21  2006/02/17 09:24:55  jonblower
63   * Change to comments
64   *
65   * Revision 1.20  2005/12/07 17:47:58  jonblower
66   * Changed "commandline" file to "args" - now just contains arguments, not program name
67   *
68   * Revision 1.19  2005/11/14 21:31:54  jonblower
69   * Got SGSRun working for SC2005 demo
70   *
71   * Revision 1.18  2005/11/11 21:57:21  jonblower
72   * Implemented passing of URLs to input files
73   *
74   * Revision 1.17  2005/11/07 21:06:42  jonblower
75   * Now allows setting of empty values for non-required parameters
76   *
77   * Revision 1.16  2005/11/04 19:31:16  jonblower
78   * Added code to disallow parameter setting while service is running
79   *
80   * Revision 1.15  2005/11/04 09:11:23  jonblower
81   * Made SGSParamFile inherit from AsyncStyxFile instead of InMemoryFile
82   *
83   * Revision 1.14  2005/11/03 07:42:47  jonblower
84   * Implemented JSAP-based parameter parsing
85   *
86   * Revision 1.10  2005/09/08 07:08:59  jonblower
87   * Removed "String user" from list of parameters to StyxFile.write()
88   *
89   * Revision 1.9  2005/08/01 16:38:05  jonblower
90   * Implemented simple parameter handling
91   *
92   * Revision 1.8  2005/07/29 16:56:07  jonblower
93   * Implementing reading command line asynchronously
94   *
95   * Revision 1.7  2005/06/20 07:17:34  jonblower
96   * Wrapped SGSParamFile as AsyncStyxFile
97   *
98   * Revision 1.6  2005/04/27 16:11:43  jonblower
99   * Added capability to add documentation files to SGS namespace
100  *
101  * Revision 1.5  2005/04/26 07:46:11  jonblower
102  * Continuing to improve setting of parameters in Styx Grid Services
103  *
104  * Revision 1.2  2005/03/26 14:30:17  jonblower
105  * Modified to use SGSConfigException
106  *
107  * Revision 1.1  2005/03/24 17:34:58  jonblower
108  * Initial import
109  *
110  */
111 public class SGSParamFile extends AsyncStyxFile
112 {
113     private static final String URL_PREFIX = "readfrom:";
114     
115     private SGSParam param; // The logical representation of the parameter
116     private StyxGridServiceInstance instance;
117     private boolean valueSet; // True if a value has been set for this parameter
118     
119     public SGSParamFile(SGSParam param, StyxGridServiceInstance instance) throws StyxException
120     {
121         // The file is named after the parameter name
122         super(new InMemoryFile(param.getName()));
123         this.param = param;
124         this.instance = instance;
125         if (this.getJSAPParameter().getDefault() != null)
126         {
127             // TODO: We are only allowing a single default value
128             this.setParameterValue(this.getJSAPParameter().getDefault()[0]);
129             this.valueSet = true;
130         }
131     }
132     
133     /***
134      * @return the JSAP Parameter object that is associated with this file
135      */
136     public Parameter getJSAPParameter()
137     {
138         return this.param.getParameter();
139     }
140     
141     /***
142      * The new value for the parameter must come in a single message (i.e.
143      * the offset must be zero and the incoming ByteBuffer must contain the
144      * entire parameter value).  Must also write with truncation.
145      */
146     public synchronized void write(StyxFileClient client, long offset,
147         int count, ByteBuffer data, boolean truncate, int tag)
148         throws StyxException
149     {
150         if (instance.getStatus() == StatusCode.RUNNING)
151         {
152             throw new StyxException("Cannot set new parameter values while service is running");
153         }
154         if (!truncate)
155         {
156             throw new StyxException("Must write to the parameter file with truncation");
157         }
158         if (offset != 0 && count != 0)
159         {
160             // We're trying to write new data to the middle of the file somewhere
161             throw new StyxException("Must write data to the start of the parameter file");
162         }
163         if (count == 0)
164         {
165             // This is an EOF message
166             if (offset != this.getLength().asLong())
167             {
168                 // We're trying to write EOF to somewhere other than the end of the file
169                 throw new StyxException("Can only write EOF to the end of this file");
170             }
171             this.replyWrite(client, 0, tag);
172         }
173         else
174         {
175             // This is a new parameter value
176             // Set the limit of the input data buffer correctly
177             data.limit(data.position() + count);
178             String newValue = StyxUtils.dataToString(data);
179 
180             this.setParameterValue(newValue);
181 
182             // If we've got this far the value must have been OK.
183             super.write(client, offset, count, data, truncate, tag);
184 
185             this.instance.argumentsChanged();
186         }
187     }
188     
189     /***
190      * @return the parameter as it will appear on the command line, including
191      * the flag, if present (e.g. "-p 12"). Returns an empty string if no 
192      * value has yet been set
193      */
194     public synchronized String getCommandLineFragment()
195     {
196         if (!this.valueSet)
197         {
198             return "";
199         }
200         if (this.getJSAPParameter() instanceof Switch)
201         {
202             Switch sw = (Switch)this.getJSAPParameter();
203             if (this.getParameterValue().equalsIgnoreCase("true"))
204             {
205                 if (sw.getLongFlag() == JSAP.NO_LONGFLAG)
206                 {
207                     return "-" + sw.getShortFlag();
208                 }
209                 else
210                 {
211                     return "--" + sw.getLongFlag();
212                 }
213             }
214             else
215             {
216                 return "";
217             }
218         }
219         else if (this.getJSAPParameter() instanceof FlaggedOption)
220         {
221             FlaggedOption fo = (FlaggedOption)this.getJSAPParameter();
222             // Use the long flag if present, if not the short one
223             if (fo.getLongFlag() == JSAP.NO_LONGFLAG)
224             {
225                 return "-" + fo.getShortFlag() + " " + this.getParameterValue();
226             }
227             else
228             {
229                 return "--" + fo.getLongFlag() + "=" + this.getParameterValue();
230             }
231         }
232         else if (this.getJSAPParameter() instanceof UnflaggedOption)
233         {
234             return this.getParameterValue();
235         }
236         else
237         {
238             // Should never get here unless we add more param types in future
239             return "";
240         }
241     }
242     
243     /***
244      * @return the current value of this parameter
245      */
246     public String getParameterValue()
247     {
248         return ((InMemoryFile)this.baseFile).getContents();
249     }
250     
251     /***
252      * Sets the parameter value, checking that the value is OK.
253      * @throws StyxException if the parameter is not valid
254      */
255     public void setParameterValue(String newValue) throws StyxException
256     {
257         // Check that the new value is valid
258         // Switches must be "true" or "false"
259         if (this.getJSAPParameter() instanceof Switch)
260         {
261             if (!newValue.equalsIgnoreCase("true") &&
262                 !newValue.equalsIgnoreCase("false"))
263             {
264                 throw new StyxException("Parameter " + this.getName() +
265                     " can only be \"true\" or \"false\"");
266             }
267         }
268         else
269         {
270             Option op = (Option)this.getJSAPParameter();
271             // Check for empty values
272             // TODO: also check type of argument (integer, float etc)
273             if (newValue.trim().equals(""))
274             {
275                 if (op.required())
276                 {
277                     throw new StyxException("Parameter " + this.getName() +
278                         " must have a non-empty value");
279                 }
280                 else
281                 {
282                     // Parameter is not required, so unset the parameter and return
283                     ((InMemoryFile)this.baseFile).setContents("");
284                     this.valueSet = false;
285                     return;
286                 }
287             }
288             else if (this.param.getInputFile() != null)
289             {
290                 // This parameter represents an input file.
291                 // For each value in this parameter, see if it is a "readfrom:<url>"
292                 // If so, do nothing: if not, add an InputFile to allow clients
293                 // to upload data to this file
294                 String[] files = newValue.split(" ");
295                 // First we must remove all previous input files that were set by
296                 // this parameter
297                 this.instance.removeInputFiles(files);
298                 for (int i = 0; i < files.length; i++)
299                 {
300                     if (!files[i].startsWith(URL_PREFIX))
301                     {
302                         // This is not a URL.
303                         this.instance.addInputFile(files[i]);
304                     }
305                 }
306             }
307             // Parameters representing output files don't show up in the namespace
308         }
309         // TODO: only set contents if the value has changed
310         ((InMemoryFile)this.baseFile).setContents(newValue);
311         this.valueSet = true;
312     }
313     
314     /***
315      * Checks to see if the contents of this file are valid.  At the moment, this
316      * just checks to see if a value has been set for a required parameter.
317      * @throws StyxException if the file contents are not valid for some reason.
318      */
319     public void checkValid() throws StyxException
320     {
321         if (this.getJSAPParameter() instanceof Option)
322         {
323             Option op = (Option)this.getJSAPParameter();
324             if (this.valueSet)
325             {
326                 if (this.param.getInputFile() != null)
327                 {
328                     // This parameter represents an input file.  See if this is a URL
329                     // and if so, download it
330                     // TODO: if this is not a URL, check that it exists
331                     String str = this.getParameterValue();
332                     if (str.startsWith(URL_PREFIX))
333                     {
334                         // This could be a URL
335                         String urlStr = str.substring(URL_PREFIX.length());
336                         try
337                         {
338                             URL url = new URL(urlStr);
339                             File urlPath = new File(url.getPath());
340                             // TODO: be cleverer about file names, particularly
341                             // watching out for name clashes
342                             String name = urlPath.getName().equals("") ? "random.dat" : urlPath.getName();
343                             this.instance.downloadFrom(url, name);
344                             // Now set the contents of this file to the new file name
345                             this.setParameterValue(name);
346                         }
347                         catch(MalformedURLException mue)
348                         {
349                             throw new StyxException(urlStr + " is not a valid URL");
350                         }
351                     }
352                 }
353             }
354             else
355             {
356                 // A value hasn't been set
357                 if (op.required())
358                 {
359                     throw new StyxException(this.name + " is a required parameter:" +
360                         " a value must be set");
361                 }
362             }
363         }
364     }
365     
366 }