View Javadoc

1   /*
2    * $Id: AbstractApplication.java,v 1.11 2005/08/10 17:45:10 clq2 Exp $
3    *
4    * Created on 13 October 2003 by Paul Harrison
5    * Copyright 2003 AstroGrid. All rights reserved.
6    *
7    * This software is published under the terms of the AstroGrid
8    * Software License version 1.2, a copy of which has been included
9    * with this distribution in the LICENSE.txt file.
10   */
11  
12  package org.astrogrid.applications;
13  
14  import org.astrogrid.applications.beans.v1.cea.castor.MessageType;
15  import org.astrogrid.applications.beans.v1.cea.castor.ResultListType;
16  import org.astrogrid.applications.beans.v1.cea.castor.types.LogLevel;
17  import org.astrogrid.applications.beans.v1.parameters.ParameterValue;
18  import org.astrogrid.applications.description.ApplicationDescription;
19  import org.astrogrid.applications.description.ApplicationInterface;
20  import org.astrogrid.applications.description.Cardinality;
21  import org.astrogrid.applications.description.ParameterDescription;
22  import org.astrogrid.applications.description.exception.ParameterDescriptionNotFoundException;
23  import org.astrogrid.applications.description.exception.ParameterNotInInterfaceException;
24  import org.astrogrid.applications.parameter.DefaultParameterAdapter;
25  import org.astrogrid.applications.parameter.ParameterAdapter;
26  import org.astrogrid.applications.parameter.ParameterAdapterException;
27  import org.astrogrid.applications.parameter.protocol.ExternalValue;
28  import org.astrogrid.applications.parameter.protocol.ProtocolLibrary;
29  import org.astrogrid.community.User;
30  import org.astrogrid.workflow.beans.v1.Tool;
31  
32  import org.apache.commons.collections.iterators.IteratorChain;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  
36  import java.io.PrintWriter;
37  import java.io.StringWriter;
38  import java.util.ArrayList;
39  import java.util.Date;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.Observable;
43  
44  /***
45   * Basic implementation of {@link org.astrogrid.applications.Application}
46   * <p />
47   * This is an abstract class that CEA providers must extend. 
48   * 
49   * <I>This class provides a lot of protected-visibility helper methods, private fields, final methods, etc - it tries to 
50   * impose some structure on the form the extension takes. This may mean that it'd be better to split this class into a framework class, and a plugin class. But we'll see</i>
51   * <h2>Abstract Methods</h2>
52   * Extenders must implement the {@link #execute()} method.
53   * <h2>Overridable Methods</h2>
54   * The {@link #instantiateAdapter(ParameterValue, ParameterDescription, IndirectParameterValue)} method may be overridden to return instances of a custom {@link org.astrogrid.applications.parameter.ParameterAdapter} if needed<br/>
55   * The default implementaiton of {@link #attemptAbort()} does nothing. Providers may extend this if suitable.<br />
56   * The {@link #createTemplateMessage()} method can also be overridden to return more application-specific information, similarly {@link #toString()} if desired.<br/>
57   * 
58   * <h2>Helper Methods</h2>
59   * This class provides helper methods for working with parameters, parameter adapters, and for progress reporting.
60  *  <h3>Parameters</h3>
61  *    There's methods to iterate through input parameters, output parameters, and all parameters, and to find input / output / either by name
62  *  <h3>Parameter Adapters</h3>
63  *    There's methods to iterate through input parameterAdapters, output parameterAdapters, and all parameterAdapters - ant to find input / output / eitther kind of adapter by parameter name.
64  *    All these methods have {@link #createAdapters()} as a precondition - this initializes the adapter sets.
65  *  <h3>Progress Reporting</h3>
66  * There's a set of methods that hide the details of notifying observers<p>
67  *  Calls to {@link #setStatus(Status)} update the status field of the application, and also send a change notification to all observers registered to this application. The 
68  * {@link #reportMessage(String)} method will send a message to each observer, as well as adding it to the log. Similarly with {@link #reportWarning}. Finally {@link #reportError}
69  * will send a message <b>and</b> set the status of this application to {@link org.astrogrid.applications.Status#ERROR}. The overridable method {@link #createTemplateMessage()} 
70  * is used to create the template messages for this methods.
71  *  @author Noel Winstanley
72   * @author Paul Harrison (pah@jb.man.ac.uk)
73   * @version $Name: HEAD $
74   * @since iteration4.1
75   * @see org.astrogrid.applications.javaclass.JavaClassApplication as an example extension
76   * @see org.astrogrid.applications.Application
77   * @see org.astrogrid.applications.description.ApplicationDescription
78   * @see org.astrogrid.applications.parameter.ParameterAdapter
79   */
80  public abstract class AbstractApplication extends Observable implements Application {
81     /*** interface to the set of identifiers for an application */
82     public static interface IDs {
83          /*** the cea-assigned id for this application execution */
84         public String getId();
85         /*** the user-assigned id for this application execution
86          * @todo rename to something less jes-specific
87          * @return id for this execution
88          */
89         public String getJobStepId();
90         /*** identifier for the user who own this application execution */
91         public User getUser();
92     }
93      /***
94       * Commons Logger for this class
95       */
96      private static final Log logger = LogFactory.getLog(AbstractApplication.class);
97  
98  
99     /*** the interface being used by this application */
100    private final ApplicationInterface applicationInterface;
101    
102    protected final IDs ids;
103    /*** list of parameter adapters for the inputs to the application. emty at start */
104    private final List inputAdapters = new ArrayList();
105    /*** library of indirection protocol handlers */
106    protected final ProtocolLibrary lib;
107    /*** list of parameter adapters for the outputs of the application. empty at start */
108    private final List outputAdapters = new ArrayList();
109    /*** list type containing results of the application execution. obviously empty to start with */
110    private final ResultListType results = new ResultListType();
111 
112    /***
113     * the application status
114     */
115    private Status status = Status.NEW;
116    /*** tool object defines the parameters, etc to the application */
117    private final Tool tool;
118    
119    /*** construct a new application execution
120     * @param ids identifiers for this application execution
121     * @param tool defines the parameters of this exeuction, and which application / interface is to be called.
122     * @param applicationInterface the descrptioni interface this application conforms to.
123     * @param lib library of indirection handlers - used to read remote parameters using various protocols
124      */
125    public AbstractApplication(IDs ids,Tool tool, ApplicationInterface applicationInterface,ProtocolLibrary lib) 
126    {      
127       this.applicationInterface = applicationInterface;
128       this.ids = ids;
129       this.tool = tool;
130       this.lib = lib;
131 
132    }
133     /*** default implementation of attemptAbort - always fails, and returns false.
134      */
135     public boolean attemptAbort() {
136         return false;
137     }
138 
139     /*** hook that specialized subclasses can overried - to return a custom adapter  
140      * used in {@link #createAdapters}
141      * @return a {@link DefaultParameterAdapter}
142      * @TODO perhaps make this create different types of adapter dependent on parameter type.
143      */
144     protected ParameterAdapter instantiateAdapter(ParameterValue pval, ParameterDescription descr,ExternalValue indirectVal) {
145         return new DefaultParameterAdapter(pval,descr,indirectVal);
146     }
147     /*** sets up the list of input and output parameter adapters
148      * must be called before any of the methods that access / query parameter adapters.
149      * @throws ParameterDescriptionNotFoundException
150      * @throws ParameterAdapterException
151      * @see #createAdapters() for customization.
152      */
153     protected final void createAdapters() throws ParameterDescriptionNotFoundException, ParameterAdapterException {
154         inputAdapters.clear();
155         outputAdapters.clear();
156         results.clearResult();
157         for  (Iterator params = inputParameterValues(); params.hasNext();){
158              ParameterValue param = (ParameterValue)params.next();       
159             ExternalValue iVal = (param.getIndirect() ? lib.getExternalValue(param) : null);
160              ParameterAdapter adapter = this.instantiateAdapter(param,getApplicationDescription().getParameterDescription(param.getName()),iVal);
161              inputAdapters.add(adapter);
162           }
163         for  (Iterator params = outputParameterValues(); params.hasNext();){
164             ParameterValue param = (ParameterValue)params.next();       
165            ExternalValue iVal = (param.getIndirect() ? lib.getExternalValue(param) : null);
166             ParameterAdapter adapter = this.instantiateAdapter(param,getApplicationDescription().getParameterDescription(param.getName()),iVal);
167             outputAdapters.add(adapter);
168             results.addResult(adapter.getWrappedParameter());
169          }          
170     }
171    
172    /***
173      * Checks that all the parameters have been specified. 
174      * It does this by looking through the interface that has been specified and checking that all the mandatory parameters have been specified at least once.
175      * It will throw exception at the first error.
176      * @TODO it would be better to gather all of the errors together and thow an exception with all of them in, rather than just the first....
177  * @throws ParameterNotInInterfaceException
178  * @throws MandatoryParameterNotPassedException
179  * @throws ParameterDescriptionNotFoundException
180      */
181     public boolean checkParameterValues()
182             throws ParameterNotInInterfaceException,
183             MandatoryParameterNotPassedException, ParameterDescriptionNotFoundException {
184         //first check that the parameters are allowed at all
185         ParameterValue[] inputs = tool.getInput().getParameter();
186         checkParametersInDefinition(inputs);
187         ParameterValue[] outputs = tool.getOutput().getParameter();
188         checkParametersInDefinition(outputs);
189                
190  
191         // Then check on parameter cardinalities
192         String[] inputNames = applicationInterface.getArrayofInputs();
193         for (int i = 0; i < inputNames.length; i++) {
194             String inputName = inputNames[i];
195             checkCardinality(inputName, true);
196 
197         }
198         String[] outputNames = applicationInterface.getArrayofOutputs();
199         for (int i = 0; i < outputNames.length; i++) {
200             String outputName = outputNames[i];
201             checkCardinality(outputName, false);
202         }
203         return true;
204    }
205 
206     /***
207      * Checks that all of the {@link ParameterValue}s in an array are allowed by the application definition. It will throw an exeception on the first error found.
208      * @param pv the array of parameter values to be checked
209      * @throws ParameterDescriptionNotFoundException
210      */
211     private void checkParametersInDefinition(ParameterValue[] pv) throws ParameterDescriptionNotFoundException
212     {
213         for (int i = 0; i < pv.length; i++) {
214             ParameterValue value = pv[i];
215             getApplicationDescription().getParameterDescription(value.getName()); //checks if the parameter exists in description
216              
217         }
218     }
219     /***
220      * Checks whether parameters specified conform with their cardinality. At the moment this only checks to see if a required parameter is missing.
221  * @param inputName the name of the paramter to be checked.
222  * @param isInput is the parameter an input parameter.
223  * @throws ParameterDescriptionNotFoundException
224  * @throws ParameterNotInInterfaceException
225  * @throws MandatoryParameterNotPassedException
226  */
227 private void checkCardinality(String inputName, boolean isInput) throws ParameterDescriptionNotFoundException, ParameterNotInInterfaceException, MandatoryParameterNotPassedException {
228     Cardinality car = applicationInterface
229             .getParameterCardinality(inputName);
230     if (car.getMinOccurs() != 0) {
231         ParameterValue pval = isInput?findInputParameter(inputName):findOutputParameter(inputName);
232         if (pval == null) {
233             throw new MandatoryParameterNotPassedException(
234                     tool.getName()+ " -- The interface " + applicationInterface.getName()
235                             + " expects "+ (isInput?"input":"output") +" parameter " + inputName);
236         }
237     }
238 }
239     /*** can be extended by subclasses to provide more info */
240    public MessageType createTemplateMessage() {
241        MessageType mt = new MessageType();
242        mt.setSource(this.toString() + "\nid:" + this.getID() + "\nassignedId: " + this.getJobStepID());
243        mt.setTimestamp(new Date());
244        mt.setPhase(status.toExecutionPhase());
245        return mt;
246    }
247 
248    /***<b>Deprecated</b> 
249     * to be overridden- should start the application running, and then return
250     * <p>
251     * Usual pattern.
252     * <ol>
253     * <li>(optional) inspect / adjust parameter values
254     * <li> call {@link #createAdapters()} to create parameterAdapters 
255     * <li>iterate through input parameter adapters, calling {@link ParameterAdapter#process()} on each, collecting returned parameter values
256     * <li>set application state to {@link Status#INITIALIZED}
257     * <li>before returning, start background thread which
258     * <ol>
259     *   <li>sets application state to {@link Status#RUNNING}
260     *   <li>performs applicatioin execution in some way, passing in parameter values
261     *   <li>reports progress via {@link #reportMessage(String)}, etc.
262     *   <li> on application complettion, set application state to {@link Status#WRITINGBACK}
263     *   <li>iterate through output parameter adapters, calling {@link ParameterAdapter#writeBack(Object)} on each.
264     *   <li>set application state to {@link Status#COMPLETED}
265     * </ol>
266     *</ol>
267     *@deprecated - use {@link #createExecutionTask()} instead
268   */
269    public  boolean execute() throws CeaException {
270        return false;
271    }
272    
273    /*** subclassing helper - find a parameter by name in the tool inputs */
274    protected final ParameterValue findInputParameter(String name) {
275        return (ParameterValue)tool.findXPathValue("input/parameter[name='" + name + "']");
276 
277    }
278    
279   // querying parameter adapters. 
280   /*** find the parameter adapter for the named input parameter */
281   protected final ParameterAdapter findInputParameterAdapter(String name) {
282       for (Iterator i = inputAdapters.iterator(); i.hasNext(); ) {
283           ParameterAdapter a = (ParameterAdapter)i.next();
284           if (a.getWrappedParameter().getName().equals(name)) {
285               return a;
286           }
287       }
288       return null;
289   }
290    /*** subclassing helper - find a parameter by name in the tool outputs */
291    protected final ParameterValue findOutputParameter(String name) {
292        return (ParameterValue)tool.findXPathValue("output/parameter[name='" + name + "']");
293        
294    }
295   /*** find the parameter adapter for the named output parameter */
296    protected final ParameterAdapter findOutputParameterAdapter(String name) {   
297       for (Iterator i = outputAdapters.iterator(); i.hasNext(); ) {
298           ParameterAdapter a = (ParameterAdapter)i.next();
299           if (a.getWrappedParameter().getName().equals(name)) {
300               return a;
301           }
302       }      
303       return null;      
304   }
305   
306     /*** subclassing helper - find a parameter by name in the tool inputs or tool outputs */
307    protected final ParameterValue findParameter(String name)   {
308       return (ParameterValue)tool.findXPathValue("input/parameter[name='" + name + "'] | output/parameter[name='" + name + "']");
309    }
310   /*** find the parameter adapter for the named parameter (which may be either input or output) */
311   protected final ParameterAdapter findParameterAdapter(String name) {
312       ParameterAdapter a = findInputParameterAdapter(name);
313       if (a == null) {
314           a = findOutputParameterAdapter(name);
315       }
316       return a;
317   }
318 
319   /*** access the description associated with this application */
320    public final ApplicationDescription getApplicationDescription() {
321       return applicationInterface.getApplicationDescription();
322    }
323  
324 /*** access the interface of {@link #getApplicationDescription()} that is to be executed */
325    public final ApplicationInterface getApplicationInterface()  {
326        return applicationInterface;
327    }
328     public final String getID() {
329         return ids.getId();
330     }
331     
332     public final Tool getTool() {
333         return tool;
334     }
335    
336    public final ParameterValue[] getInputParameters() {
337        return tool.getInput().getParameter();
338    }
339 
340    public final String getJobStepID() {
341       return ids.getJobStepId();
342    }
343 
344    
345    public final ResultListType getResult() {
346        return results;
347    }
348    
349 
350    /***
351     * @return
352     */
353    public final  Status getStatus() {
354       return status;
355    }
356 
357    public final User getUser() {
358       return ids.getUser();
359    }
360    /*** iterator over all parameter values in the tool inputs */
361    protected final  Iterator inputParameterValues() {
362        return tool.findXPathIterator("input/parameter");
363    }
364    /*** iterate over all parameter values in the tool outputs */
365    protected final Iterator outputParameterValues() {
366        return tool.findXPathIterator("output/parameter");
367    }
368    /*** iterator over all parameterValues in the tool inputs and outputs */
369    protected final Iterator parameterValues() {
370        return tool.findXPathIterator("input/parameter | output/parameter");
371    }
372    
373    /*** iterator over all input parameter adapters */
374    protected final Iterator inputParameterAdapters() {
375        return inputAdapters.iterator();
376    } 
377    /*** iterator over all output parameter adapters */
378    protected final Iterator outputParameterAdapters() {
379        return outputAdapters.iterator();
380    }
381    /*** iterator over all input and output parameter adapters */
382    protected final Iterator parameterAdapters() {
383        IteratorChain i = new IteratorChain();
384        i.addIterator(inputAdapters.iterator());
385        i.addIterator(outputAdapters.iterator());       
386        return i;
387    }
388 
389     /*** report an error message - to the log, and to all observers 
390      * sets status to {@link Status#ERROR}
391      * @see #setStatus(Status)
392      * */
393     protected final void reportError(String msg) {
394         logger.error(msg); 
395         MessageType mt = createTemplateMessage();
396         mt.setContent(msg);
397         mt.setLevel(LogLevel.ERROR);
398         setChanged();
399         notifyObservers(mt);
400         setStatus(Status.ERROR);        
401     }
402     /*** report an exception - to the log, and to all observers 
403      * sets status to {@link Status#ERROR}
404      * @see #setStatus(Status)
405      * */
406     protected final void reportError(String msg, Throwable e) {
407         logger.error(msg,e); 
408         MessageType mt = createTemplateMessage();
409         StringWriter sw = new StringWriter();
410         PrintWriter pw = new PrintWriter(sw);
411         pw.println(msg);
412         pw.println(e.getMessage());
413         e.printStackTrace(pw);
414         pw.close();
415         mt.setContent(sw.toString());
416         mt.setLevel(LogLevel.ERROR);
417         setChanged();
418         notifyObservers(mt);
419         setStatus(Status.ERROR);
420         
421     }
422     /*** report a warning - to the log and all observers.
423      * does not change status of application
424      * @param msg
425      */
426     protected final void reportWarning(String msg) {
427         logger.warn(msg);
428         MessageType mt = createTemplateMessage();
429         mt.setContent(msg);
430         mt.setLevel(LogLevel.WARN);
431         setChanged();
432         notifyObservers(mt);
433     }
434     
435     /*** report a warning - to the log and all observers
436      * does not change status of applicaiton
437      * @param msg
438      * @param e
439      */
440     protected final void reportWarning(String msg,Throwable e) {
441         logger.warn(msg,e);
442         MessageType  mt = createTemplateMessage();
443         StringWriter sw = new StringWriter();
444         PrintWriter pw = new PrintWriter(sw);
445         pw.println(msg);
446         pw.println(e.getMessage());
447         e.printStackTrace(pw);
448         pw.close();
449         mt.setContent(sw.toString());
450         mt.setLevel(LogLevel.WARN);
451         setChanged();
452         notifyObservers(mt);
453     }
454     
455     /*** report an arbitrary message - to the log, and also to all observers */
456     protected final void reportMessage(String msg) {
457         logger.info(msg); 
458         MessageType mt = createTemplateMessage();
459         mt.setContent(msg);
460         mt.setLevel(LogLevel.INFO);
461         setChanged();
462         notifyObservers(mt);         
463     }
464 
465    /***
466     * change the status of this application and notify all observers 
467     * @param status
468     */
469    public final void setStatus(Status status) {
470       this.status = status;
471       setChanged();
472       notifyObservers(status);
473    }
474 
475 
476    public String toString() {
477       return getApplicationDescription().getName() + "#" + getApplicationInterface().getName();
478    }
479 
480    /**** to be implemented - create a runnable for applicaton execution, and then return
481     * <p>
482     * Usual body of method does
483     * <ol>
484     * <li>(optional) inspect / adjust parameter values
485     * <li> call {@link #createAdapters()} to create parameterAdapters
486     * <li> create runnable result object
487     * </ol>
488     * responsibilities of runnable are
489     * <ol> 
490     * <li>iterate through input parameter adapters, calling {@link ParameterAdapter#process()} on each, collecting returned parameter values
491     * <li>set application state to {@link Status#INITIALIZED}
492     *   <li>sets application state to {@link Status#RUNNING}
493     *   <li>performs applicatioin execution in some way, passing in parameter values
494     *   <li>reports progress via {@link #reportMessage(String)}, etc.
495     *   <li> on application complettion, set application state to {@link Status#WRITINGBACK}
496     *   <li>iterate through output parameter adapters, calling {@link ParameterAdapter#writeBack(Object)} on each.
497     *   <li>set application state to {@link Status#COMPLETED}
498     * </ol>
499     *</ol>
500     * 
501     *  default implementation of this method - for backwards compatability
502     * if this method returns null, then the deprecated {@link #execute()} will be called instead
503     * @see org.astrogrid.applications.Application#createExecutionTask()
504     * @return null 
505     */
506     public Runnable createExecutionTask() throws CeaException {
507         return null;
508     }
509 }
510