001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: JSExecutor.java,v 1.7 2007/02/27 14:34:23 drmlipp Exp $
021: *
022: * $Log: JSExecutor.java,v $
023: * Revision 1.7 2007/02/27 14:34:23 drmlipp
024: * Some refactoring to reduce cyclic dependencies.
025: *
026: * Revision 1.6 2007/01/22 16:23:14 drmlipp
027: * Fixed conversion of JS XML result with namespaces.
028: *
029: * Revision 1.5 2006/11/19 21:53:47 mlipp
030: * Finished support for native Java types.
031: *
032: * Revision 1.4 2006/11/17 12:19:25 drmlipp
033: * Added access to activity's unique key from JS.
034: *
035: * Revision 1.3 2006/09/29 12:32:13 drmlipp
036: * Consistently using WfMOpen as projct name now.
037: *
038: * Revision 1.2 2006/03/08 14:46:44 drmlipp
039: * Synchronized with 1.3.3p5.
040: *
041: * Revision 1.1.1.4.6.5 2006/03/07 13:51:31 drmlipp
042: * Finished transition to E4X usage for actual parameter evaluation.
043: *
044: * Revision 1.1.1.4.6.4 2006/02/08 19:29:46 drmlipp
045: * Added E4X support.
046: *
047: * Revision 1.1.1.4.6.3 2005/12/20 22:05:31 drmlipp
048: * Removed deprecated exceptions and completed argument convertion.
049: *
050: * Revision 1.1.1.4.6.2 2005/12/15 22:55:54 drmlipp
051: * Continued XML support for JS tool.
052: *
053: * Revision 1.1.1.4.6.1 2005/12/11 20:31:34 drmlipp
054: * Evaluating JavaScript XML result.
055: *
056: * Revision 1.1.1.4 2004/08/18 15:17:39 drmlipp
057: * Update to 1.2
058: *
059: * Revision 1.46 2004/04/12 19:33:52 lipp
060: * Clarified application invocation interface.
061: *
062: * Revision 1.45 2004/04/01 13:28:58 lipp
063: * Improved naming once more.
064: *
065: * Revision 1.44 2004/04/01 09:42:32 lipp
066: * Adapted context name.
067: *
068: * Revision 1.43 2004/04/01 09:32:07 lipp
069: * Improved tool agent context implementtaion.
070: *
071: * Revision 1.42 2004/03/31 19:36:20 lipp
072: * Completed implementation of Activity.abandon(String).
073: *
074: * Revision 1.41 2004/03/29 11:46:17 lipp
075: * Preliminary version of "abandoned" for JSExceutor.
076: *
077: * Revision 1.40 2004/03/28 20:41:10 lipp
078: * Started implementing call to abandon from JSExecutor.
079: *
080: * Revision 1.39 2004/02/20 09:53:01 lipp
081: * Fixed exception handling.
082: *
083: * Revision 1.38 2004/02/13 10:01:34 lipp
084: * Changed result type for result provider to Map which is more
085: * appropriate.
086: *
087: * Revision 1.37 2004/01/26 15:11:19 montag
088: * Tool JSExecutor now returns SchemaType objects.
089: *
090: * Revision 1.36 2003/11/26 17:00:37 lipp
091: * Using new ResultProvider.
092: *
093: * Revision 1.35 2003/11/26 15:21:18 lipp
094: * Proper handling of (top-level) RemoteException.
095: *
096: * Revision 1.34 2003/09/24 13:45:52 lipp
097: * Added support for Date type in process relevant data.
098: *
099: * Revision 1.33 2003/06/27 08:51:44 lipp
100: * Fixed copyright/license information.
101: *
102: * Revision 1.32 2003/06/17 14:19:07 lipp
103: * Improved comment.
104: *
105: * Revision 1.31 2003/05/15 07:46:42 lipp
106: * Proper handling of JavaScript default double result.
107: *
108: * Revision 1.30 2003/05/06 13:21:30 lipp
109: * Resolved cyclic dependency.
110: *
111: * Revision 1.29 2003/05/02 14:55:58 lipp
112: * Resolved some more package dependencies.
113: *
114: * Revision 1.28 2003/04/26 16:11:14 lipp
115: * Moved some classes to reduce package dependencies.
116: *
117: * Revision 1.27 2003/04/25 14:50:58 lipp
118: * Fixed javadoc errors and warnings.
119: *
120: * Revision 1.26 2003/04/02 11:20:06 lipp
121: * Moved type adaption to framework.
122: *
123: * Revision 1.25 2003/04/02 09:30:05 lipp
124: * Supporting more data types.
125: *
126: * Revision 1.24 2003/03/31 16:50:28 huaiyang
127: * Logging using common-logging.
128: *
129: * Revision 1.23 2003/03/28 15:41:09 lipp
130: * Removed no longer needed running trace.
131: *
132: * Revision 1.22 2003/02/12 11:57:31 lipp
133: * Improved deadlock (RemoteException) handling for tools. Imroved debug
134: * information.
135: *
136: * Revision 1.21 2003/02/11 14:50:48 lipp
137: * Better exception/logger messages.
138: *
139: * Revision 1.20 2002/12/19 21:37:42 lipp
140: * Reorganized interfaces.
141: *
142: * Revision 1.19 2002/11/22 13:16:44 lipp
143: * Made ResourceNotAvailableException a RemoteException.
144: *
145: * Revision 1.18 2002/11/20 09:26:11 lipp
146: * New method doFinish for better transaction handling in tools.
147: *
148: * Revision 1.17 2002/11/11 09:52:54 lipp
149: * Added retries for result actions.
150: *
151: * Revision 1.16 2002/10/06 20:14:38 lipp
152: * Updated argument handling.
153: *
154: * Revision 1.15 2002/10/02 12:15:49 lipp
155: * Fixed logging bug.
156: *
157: * Revision 1.14 2002/10/02 10:58:13 lipp
158: * Modifications for tool invocation.
159: *
160: * Revision 1.13 2002/09/30 13:06:03 lipp
161: * Removed access to activity.
162: *
163: * Revision 1.12 2002/09/25 15:15:47 lipp
164: * Towards full functionallity...
165: *
166: * Revision 1.11 2002/09/24 15:53:52 lipp
167: * Now really invoking...
168: *
169: * Revision 1.10 2002/09/24 12:25:19 lipp
170: * setResult implemented.
171: *
172: * Revision 1.9 2002/09/23 20:31:28 lipp
173: * Implemented async/sync invocation.
174: *
175: * Revision 1.8 2002/09/23 15:12:39 lipp
176: * Extended tool implementation definition and usage.
177: *
178: * Revision 1.7 2002/09/23 12:00:20 huaiyang
179: * Remove the mothods of set/getEmailAddress.
180: *
181: * Revision 1.6 2002/09/22 19:57:09 lipp
182: * Fixed doc error, prepared execution loop.
183: *
184: * Revision 1.5 2002/09/17 13:54:01 huaiyang
185: * Add method of setEmailaddress.
186: *
187: * Revision 1.4 2002/09/17 09:20:12 lipp
188: * Added ApplicationNotStoppedException.
189: *
190: * Revision 1.3 2002/09/16 15:30:41 huaiyang
191: * Identify it as seriazable so that can be called remotely.
192: *
193: * Revision 1.2 2002/09/11 14:16:54 lipp
194: * Extended attributes.
195: *
196: * Revision 1.1 2002/09/02 11:03:37 lipp
197: * Started javascript tool.
198: *
199: */
200: package de.danet.an.workflow.tools.rhino;
201:
202: import java.io.IOException;
203: import java.io.Reader;
204: import java.io.Serializable;
205: import java.io.StringReader;
206:
207: import java.util.Date;
208: import java.util.HashMap;
209: import java.util.Map;
210:
211: import java.rmi.RemoteException;
212:
213: import org.apache.xmlbeans.XmlCursor;
214: import org.apache.xmlbeans.XmlException;
215: import org.apache.xmlbeans.XmlObject;
216: import org.apache.xmlbeans.XmlOptions;
217: import org.apache.xmlbeans.XmlSaxHandler;
218: import org.mozilla.javascript.Context;
219: import org.mozilla.javascript.EvaluatorException;
220: import org.mozilla.javascript.Function;
221: import org.mozilla.javascript.JavaScriptException;
222: import org.mozilla.javascript.NotAFunctionException;
223: import org.mozilla.javascript.PropertyException;
224: import org.mozilla.javascript.Scriptable;
225: import org.mozilla.javascript.ScriptableObject;
226: import org.mozilla.javascript.Wrapper;
227: import org.mozilla.javascript.xml.XMLObject;
228: import org.xml.sax.ContentHandler;
229: import org.xml.sax.SAXException;
230:
231: import de.danet.an.util.sax.BodyFilter;
232: import de.danet.an.util.sax.NamespaceAttributesFilter;
233: import de.danet.an.workflow.api.Activity;
234: import de.danet.an.workflow.api.ActivityUniqueKey;
235: import de.danet.an.workflow.api.ExternalReference;
236: import de.danet.an.workflow.api.FormalParameter;
237: import de.danet.an.workflow.api.SAXEventBuffer;
238:
239: import de.danet.an.workflow.spis.aii.ApplicationNotStoppedException;
240: import de.danet.an.workflow.spis.aii.CannotExecuteException;
241: import de.danet.an.workflow.spis.aii.ContextRequester;
242: import de.danet.an.workflow.spis.aii.ResultProvider;
243: import de.danet.an.workflow.spis.aii.ResultProvider.ExceptionResult;
244: import de.danet.an.workflow.spis.aii.ToolAgent;
245: import de.danet.an.workflow.spis.aii.ToolAgentContext;
246: import de.danet.an.workflow.util.SAXEventBufferImpl;
247: import de.danet.an.workflow.util.XPDLUtil;
248:
249: /**
250: * This class provides a tool that executes JavaScript.
251: *
252: * <P>Enabling log level debug for this class logs invokation, the
253: * script to be executed, completion and termination.
254: *
255: * @author <a href="mailto:lipp@danet.de"></a>
256: * @version $Revision: 1.7 $
257: */
258:
259: public class JSExecutor implements ToolAgent, ResultProvider,
260: ContextRequester, Serializable {
261:
262: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
263: .getLog(JSExecutor.class);
264:
265: /** The script to be executed on invocation. */
266: private String script = null;
267:
268: /** The tool agent context. */
269: private ThreadLocal ctx = new ThreadLocal();
270:
271: /** The result container. */
272: private ThreadLocal result = new ThreadLocal();
273:
274: /**
275: * Creates an instance of <code>JSExecutor</code>
276: * with all attributes initialized to default values.
277: */
278: public JSExecutor() {
279: }
280:
281: /**
282: * Get the value of script.
283: * @return value of script.
284: * @see #setScript
285: */
286: public String getScript() {
287: return script;
288: }
289:
290: /**
291: * Set the value of script.
292: * @param newScript value to assign to script.
293: * @see #getScript
294: */
295: public void setScript(String newScript) {
296: this .script = newScript;
297: }
298:
299: /**
300: * Makes a context available to the tool agent.
301: * @param context the tool agent context
302: */
303: public void setToolAgentContext(ToolAgentContext context) {
304: ctx.set(context);
305: }
306:
307: /**
308: * Describe <code>invoke</code> method here.
309: *
310: * @param activity a <code>WfActivity</code> value
311: * @param formPars the formal parameters.
312: * @param map a <code>Map</code> value
313: * @throws CannotExecuteException if an error occurs
314: * @throws RemoteException if a system level error occurs
315: */
316: public void invoke(Activity activity, FormalParameter[] formPars,
317: Map map) throws CannotExecuteException, RemoteException {
318: ActivityUniqueKey auk = null;
319: try {
320: // Do not attempt to perform any operation on the activity
321: // except key() and uniqueKey() before executing the
322: // script. Else, the activity becomes part of the EJB
323: // transaction and is locked, i.e. all accesses (even
324: // display in the management client) are deferred until
325: // the javascript has completed. The time this takes is
326: // not controllable.
327: auk = activity.uniqueKey();
328: if (logger.isDebugEnabled()) {
329: logger.debug("Invoked for " + auk + ", script:");
330: logger.debug(script);
331: }
332: if (script == null) {
333: throw new CannotExecuteException("No script.");
334: }
335: Context cx = Context.enter();
336: Scriptable scope = prepareScope(cx, activity, formPars, map);
337: Reader sr = new StringReader(script);
338: cx.evaluateReader(scope, sr, "<script>", 1, null);
339: result.set(convertResult(cx, scope, formPars));
340: } catch (AbandonedException e) {
341: // thrown when the activity was deliberatly abandoned.
342: result.set(new ExceptionResult(e.getMessage()));
343: } catch (RemoteException e) {
344: throw e;
345: } catch (IOException e) {
346: logger.error(e.getMessage(), e);
347: throw new CannotExecuteException(e.getMessage());
348: } catch (SAXException e) {
349: logger.error(e.getMessage(), e);
350: throw new CannotExecuteException(e.getMessage());
351: } catch (JavaScriptException e) {
352: logger.error(e.getMessage(), e);
353: throw new CannotExecuteException(e.getMessage());
354: } finally {
355: Context.exit();
356: if (logger.isDebugEnabled()) {
357: logger.debug("Finished invocation of " + auk);
358: }
359: }
360: }
361:
362: /**
363: * A base class for creating context methods.
364: */
365: public abstract class FunctionBase extends ScriptableObject
366: implements Function {
367:
368: /**
369: * The class name.
370: * @return the class name
371: */
372: public String getClassName() {
373: return "FunctionBase";
374: }
375:
376: /**
377: * Instances cannot be called as constructors.
378: * @param cx the context
379: * @param scope the scope
380: * @param args the arguments
381: * @return never returns a result
382: * @throws JavaScriptException if an error occurs
383: */
384: public Scriptable construct(Context cx, Scriptable scope,
385: Object[] args) throws JavaScriptException {
386: throw new EvaluatorException(
387: "Function cannot be used as constructor");
388: }
389: };
390:
391: /**
392: * Prepare an environment for evaluating the script.
393: * @param cx the context.
394: * @param activity the activity.
395: * @param formPars the formal parameter definitons.
396: * @param map the actual parameter values.
397: * @return
398: * @throws JavaScriptException
399: */
400: private Scriptable prepareScope(Context cx, Activity activity,
401: FormalParameter[] formPars, Map map) throws RemoteException {
402: Scriptable scope = cx.initStandardObjects(null);
403: ScriptableObject wfe = new ScriptableObject() {
404: public String getClassName() {
405: return "ToolAgentContext";
406: }
407: };
408: Function fo = new FunctionBase() {
409: public Object call(Context cx, Scriptable scope,
410: Scriptable this Obj, Object[] args)
411: throws JavaScriptException {
412: if (logger.isDebugEnabled()) {
413: logger.debug("abandon called with: " + args[0]);
414: }
415: throw new AbandonedException((String) args[0]);
416: }
417: };
418: wfe.defineProperty("abandon", fo, ScriptableObject.PERMANENT);
419: wfe.defineProperty("activityUniqueKey", Context.javaToJS(
420: activity.uniqueKey(), scope),
421: ScriptableObject.PERMANENT | ScriptableObject.READONLY);
422: scope.put("scriptingContext", scope, wfe);
423:
424: prepareArguments(cx, scope, formPars, map);
425: return scope;
426: }
427:
428: /**
429: * @param cx the context.
430: * @param scope the scope.
431: * @param formPars the formal parameter definitons.
432: * @param map the actual parameter values.
433: */
434: protected void prepareArguments(Context cx, Scriptable scope,
435: FormalParameter[] formPars, Map map) {
436: ScriptableObject args = new ScriptableObject() {
437: public String getClassName() {
438: return "Arguments";
439: }
440: };
441: for (int i = 0; i < formPars.length; i++) {
442: String fp = formPars[i].id();
443: args
444: .defineProperty(
445: fp,
446: convertArgument(cx, scope, formPars[i]
447: .type(), map.get(fp)),
448: formPars[i].mode() == FormalParameter.Mode.IN ? ScriptableObject.PERMANENT
449: | ScriptableObject.READONLY
450: : ScriptableObject.PERMANENT);
451: }
452: scope.put("args", scope, args);
453: }
454:
455: /**
456: * Additionally convert SchemaType arguments
457: * @see JSExecutor#convertArgument
458: */
459: protected Object convertArgument(Context cx, Scriptable scope,
460: Object argType, Object argument) {
461: if (argType instanceof ExternalReference) {
462: if (XPDLUtil.isJavaType((ExternalReference) argType)) {
463: return Context.javaToJS(argument, scope);
464: }
465: }
466: return argument;
467: }
468:
469: /**
470: * Convert the result.
471: * @param cx the context.
472: * @param scope the scope.
473: * @param formPars the formal parameter definitons.
474: * @return
475: * @throws JavaScriptException
476: * @throws SAXException
477: */
478: protected Map convertResult(Context cx, Scriptable scope,
479: FormalParameter[] fps) throws JavaScriptException,
480: SAXException {
481: Scriptable sres = (Scriptable) scope.get("args", scope);
482: Map resData = new HashMap();
483: for (int i = 0; i < fps.length; i++) {
484: if (fps[i].mode() == FormalParameter.Mode.IN) {
485: continue;
486: }
487: String fpn = fps[i].id();
488: Object v = sres.get(fpn, sres);
489: v = convertResultValue(cx, scope, fps[i], v);
490: resData.put(fpn, v);
491: }
492: return resData;
493: }
494:
495: /**
496: * @param cx the context.
497: * @param scope the scope.
498: * @param formPars the formal parameter definitons.
499: * @param value
500: * @return
501: * @throws JavaScriptException
502: * @throws SAXException
503: */
504: protected Object convertResultValue(Context cx, Scriptable scope,
505: FormalParameter fp, Object value)
506: throws JavaScriptException, SAXException {
507: if (fp.type() instanceof ExternalReference) {
508: if (XPDLUtil.isJavaType((ExternalReference) fp.type())) {
509: try {
510: return Context
511: .jsToJava(value, XPDLUtil
512: .getJavaType((ExternalReference) fp
513: .type()));
514: } catch (ClassNotFoundException e) {
515: (new IllegalArgumentException(e.getMessage()))
516: .initCause(e);
517: }
518: }
519: }
520: if (fp.type() == Long.class && (value instanceof Double)) {
521: return new Long(((Double) value).longValue());
522: }
523: if (value instanceof Wrapper) {
524: return ((Wrapper) value).unwrap();
525: }
526: if (value instanceof XMLObject) {
527: SAXEventBufferImpl seb = new SAXEventBufferImpl();
528: if (((XMLObject) value).getClassName().equals("XMLList")) {
529: seb.startDocument();
530: for (int i = 0; true; i++) {
531: Object item = ((XMLObject) value).get(i,
532: (XMLObject) value);
533: if (item.equals(Scriptable.NOT_FOUND)) {
534: break;
535: }
536: xmlObjectToSax(seb, (XMLObject) item, true);
537: }
538: seb.endDocument();
539: } else {
540: xmlObjectToSax(seb, (XMLObject) value, false);
541: }
542: seb.pack();
543: return seb;
544: }
545: if ((value instanceof Scriptable)
546: && ((Scriptable) value).getClassName().equals("Date")) {
547: Scriptable s = (Scriptable) value;
548: Object gt = Scriptable.NOT_FOUND;
549: for (Scriptable c = s; c != null
550: && gt.equals(Scriptable.NOT_FOUND); c = c
551: .getPrototype()) {
552: gt = c.get("getTime", s);
553: }
554: Number millis = (Number) ((Function) gt).call(cx, scope, s,
555: new Object[] {});
556: return new Date(millis.longValue());
557: }
558: return value;
559: }
560:
561: /**
562: * Serialize a JavaScript XML object into the SAX event buffer.
563: *
564: * @param seb the SAX event buffer
565: * @param xmlObj the XML object
566: * @param fragment if <code>startDocument</code> and
567: * <code>endDocument</code> events are to be suppressed
568: * @throws SAXException
569: */
570: private void xmlObjectToSax(SAXEventBufferImpl seb,
571: XMLObject xmlObj, boolean fragment) throws SAXException {
572: Wrapper wrap = (Wrapper) ScriptableObject.callMethod(
573: (XMLObject) xmlObj, "getXmlObject", new Object[0]);
574: XmlObject result = (XmlObject) wrap.unwrap();
575: XmlCursor cursor = result.newCursor();
576: result = cursor.getObject();
577: ContentHandler ch = new NamespaceAttributesFilter(seb);
578: if (fragment) {
579: ch = new BodyFilter(ch);
580: }
581: result.save(ch, seb, (new XmlOptions()).setSaveOuter());
582: }
583:
584: /* Comment copied from interface. */
585: public Object result() {
586: Object res = result.get();
587: result.set(null);
588: return res;
589: }
590:
591: /**
592: * Describe <code>terminate</code> method here.
593: *
594: * @param activity a <code>WfActivity</code> value
595: * @throws ApplicationNotStoppedException if the application could
596: * not be terminated.
597: */
598: public void terminate(Activity activity)
599: throws ApplicationNotStoppedException {
600: throw new ApplicationNotStoppedException(
601: "Terminate not implemented for JSExecutor.");
602: }
603: }
|