1
2
3
4
5
6
7
8
9
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
185 ParameterValue[] inputs = tool.getInput().getParameter();
186 checkParametersInDefinition(inputs);
187 ParameterValue[] outputs = tool.getOutput().getParameter();
188 checkParametersInDefinition(outputs);
189
190
191
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());
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
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