Package de.danet.an.workflow.tools.rhino

Source Code of de.danet.an.workflow.tools.rhino.JSExecutor$FunctionBase

/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* $Id: JSExecutor.java 2284 2007-02-27 14:34:23Z drmlipp $
*
* $Log$
* Revision 1.6  2007/01/22 16:23:14  drmlipp
* Fixed conversion of JS XML result with namespaces.
*
* Revision 1.5  2006/11/19 21:53:47  mlipp
* Finished support for native Java types.
*
* Revision 1.4  2006/11/17 12:19:25  drmlipp
* Added access to activity's unique key from JS.
*
* Revision 1.3  2006/09/29 12:32:13  drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.2  2006/03/08 14:46:44  drmlipp
* Synchronized with 1.3.3p5.
*
* Revision 1.1.1.4.6.5  2006/03/07 13:51:31  drmlipp
* Finished transition to E4X usage for actual parameter evaluation.
*
* Revision 1.1.1.4.6.4  2006/02/08 19:29:46  drmlipp
* Added E4X support.
*
* Revision 1.1.1.4.6.3  2005/12/20 22:05:31  drmlipp
* Removed deprecated exceptions and completed argument convertion.
*
* Revision 1.1.1.4.6.2  2005/12/15 22:55:54  drmlipp
* Continued XML support for JS tool.
*
* Revision 1.1.1.4.6.1  2005/12/11 20:31:34  drmlipp
* Evaluating JavaScript XML result.
*
* Revision 1.1.1.4  2004/08/18 15:17:39  drmlipp
* Update to 1.2
*
* Revision 1.46  2004/04/12 19:33:52  lipp
* Clarified application invocation interface.
*
* Revision 1.45  2004/04/01 13:28:58  lipp
* Improved naming once more.
*
* Revision 1.44  2004/04/01 09:42:32  lipp
* Adapted context name.
*
* Revision 1.43  2004/04/01 09:32:07  lipp
* Improved tool agent context implementtaion.
*
* Revision 1.42  2004/03/31 19:36:20  lipp
* Completed implementation of Activity.abandon(String).
*
* Revision 1.41  2004/03/29 11:46:17  lipp
* Preliminary version of "abandoned" for JSExceutor.
*
* Revision 1.40  2004/03/28 20:41:10  lipp
* Started implementing call to abandon from JSExecutor.
*
* Revision 1.39  2004/02/20 09:53:01  lipp
* Fixed exception handling.
*
* Revision 1.38  2004/02/13 10:01:34  lipp
* Changed result type for result provider to Map which is more
* appropriate.
*
* Revision 1.37  2004/01/26 15:11:19  montag
* Tool JSExecutor now returns SchemaType objects.
*
* Revision 1.36  2003/11/26 17:00:37  lipp
* Using new ResultProvider.
*
* Revision 1.35  2003/11/26 15:21:18  lipp
* Proper handling of (top-level) RemoteException.
*
* Revision 1.34  2003/09/24 13:45:52  lipp
* Added support for Date type in process relevant data.
*
* Revision 1.33  2003/06/27 08:51:44  lipp
* Fixed copyright/license information.
*
* Revision 1.32  2003/06/17 14:19:07  lipp
* Improved comment.
*
* Revision 1.31  2003/05/15 07:46:42  lipp
* Proper handling of JavaScript default double result.
*
* Revision 1.30  2003/05/06 13:21:30  lipp
* Resolved cyclic dependency.
*
* Revision 1.29  2003/05/02 14:55:58  lipp
* Resolved some more package dependencies.
*
* Revision 1.28  2003/04/26 16:11:14  lipp
* Moved some classes to reduce package dependencies.
*
* Revision 1.27  2003/04/25 14:50:58  lipp
* Fixed javadoc errors and warnings.
*
* Revision 1.26  2003/04/02 11:20:06  lipp
* Moved type adaption to framework.
*
* Revision 1.25  2003/04/02 09:30:05  lipp
* Supporting more data types.
*
* Revision 1.24  2003/03/31 16:50:28  huaiyang
* Logging using common-logging.
*
* Revision 1.23  2003/03/28 15:41:09  lipp
* Removed no longer needed running trace.
*
* Revision 1.22  2003/02/12 11:57:31  lipp
* Improved deadlock (RemoteException) handling for tools. Imroved debug
* information.
*
* Revision 1.21  2003/02/11 14:50:48  lipp
* Better exception/logger messages.
*
* Revision 1.20  2002/12/19 21:37:42  lipp
* Reorganized interfaces.
*
* Revision 1.19  2002/11/22 13:16:44  lipp
* Made ResourceNotAvailableException a RemoteException.
*
* Revision 1.18  2002/11/20 09:26:11  lipp
* New method doFinish for better transaction handling in tools.
*
* Revision 1.17  2002/11/11 09:52:54  lipp
* Added retries for result actions.
*
* Revision 1.16  2002/10/06 20:14:38  lipp
* Updated argument handling.
*
* Revision 1.15  2002/10/02 12:15:49  lipp
* Fixed logging bug.
*
* Revision 1.14  2002/10/02 10:58:13  lipp
* Modifications for tool invocation.
*
* Revision 1.13  2002/09/30 13:06:03  lipp
* Removed access to activity.
*
* Revision 1.12  2002/09/25 15:15:47  lipp
* Towards full functionallity...
*
* Revision 1.11  2002/09/24 15:53:52  lipp
* Now really invoking...
*
* Revision 1.10  2002/09/24 12:25:19  lipp
* setResult implemented.
*
* Revision 1.9  2002/09/23 20:31:28  lipp
* Implemented async/sync invocation.
*
* Revision 1.8  2002/09/23 15:12:39  lipp
* Extended tool implementation definition and usage.
*
* Revision 1.7  2002/09/23 12:00:20  huaiyang
* Remove the mothods of set/getEmailAddress.
*
* Revision 1.6  2002/09/22 19:57:09  lipp
* Fixed doc error, prepared execution loop.
*
* Revision 1.5  2002/09/17 13:54:01  huaiyang
* Add method of setEmailaddress.
*
* Revision 1.4  2002/09/17 09:20:12  lipp
* Added ApplicationNotStoppedException.
*
* Revision 1.3  2002/09/16 15:30:41  huaiyang
* Identify it as seriazable so that can be called remotely.
*
* Revision 1.2  2002/09/11 14:16:54  lipp
* Extended attributes.
*
* Revision 1.1  2002/09/02 11:03:37  lipp
* Started javascript tool.
*
*/
package de.danet.an.workflow.tools.rhino;

import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import java.rmi.RemoteException;

import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlSaxHandler;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NotAFunctionException;
import org.mozilla.javascript.PropertyException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Wrapper;
import org.mozilla.javascript.xml.XMLObject;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import de.danet.an.util.sax.BodyFilter;
import de.danet.an.util.sax.NamespaceAttributesFilter;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.ExternalReference;
import de.danet.an.workflow.api.FormalParameter;
import de.danet.an.workflow.api.SAXEventBuffer;

import de.danet.an.workflow.spis.aii.ApplicationNotStoppedException;
import de.danet.an.workflow.spis.aii.CannotExecuteException;
import de.danet.an.workflow.spis.aii.ContextRequester;
import de.danet.an.workflow.spis.aii.ResultProvider;
import de.danet.an.workflow.spis.aii.ResultProvider.ExceptionResult;
import de.danet.an.workflow.spis.aii.ToolAgent;
import de.danet.an.workflow.spis.aii.ToolAgentContext;
import de.danet.an.workflow.util.SAXEventBufferImpl;
import de.danet.an.workflow.util.XPDLUtil;

/**
* This class provides a tool that executes JavaScript.
*
* <P>Enabling log level debug for this class logs invokation, the
* script to be executed, completion and termination.
*
* @author <a href="mailto:lipp@danet.de"></a>
* @version $Revision: 2284 $
*/

public class JSExecutor implements ToolAgent, ResultProvider, ContextRequester,
           Serializable {

    private static final org.apache.commons.logging.Log logger
  = org.apache.commons.logging.LogFactory.getLog(JSExecutor.class);

    /** The script to be executed on invocation. */
    private String script = null;

    /** The tool agent context. */
    private ThreadLocal ctx = new ThreadLocal ();

    /** The result container. */
    private ThreadLocal result = new ThreadLocal ();

    /**
     * Creates an instance of <code>JSExecutor</code>
     * with all attributes initialized to default values.
     */
    public JSExecutor () { 
    }

    /**
     * Get the value of script.
     * @return value of script.
     * @see #setScript
     */
    public String getScript() {
  return script;
    }
   
    /**
     * Set the value of script.
     * @param newScript value to assign to script.
     * @see #getScript
     */
    public void setScript(String newScript) {
  this.script = newScript;
    }

    /**
     * Makes a context available to the tool agent.
     * @param context the tool agent context
     */
    public void setToolAgentContext (ToolAgentContext context) {
  ctx.set(context);
    }

    /**
     * Describe <code>invoke</code> method here.
     *
     * @param activity a <code>WfActivity</code> value
     * @param formPars the formal parameters.
     * @param map a <code>Map</code> value
     * @throws CannotExecuteException if an error occurs
     * @throws RemoteException if a system level error occurs
     */
    public void invoke(Activity activity, FormalParameter[] formPars, Map map)
  throws CannotExecuteException, RemoteException {
  ActivityUniqueKey auk = null;
  try {
      // Do not attempt to perform any operation on the activity
      // except key() and uniqueKey() before executing the
      // script. Else, the activity becomes part of the EJB
      // transaction and is locked, i.e. all accesses (even
      // display in the management client) are deferred until
      // the javascript has completed. The time this takes is
      // not controllable.
      auk = activity.uniqueKey();
      if (logger.isDebugEnabled()) {
    logger.debug ("Invoked for " + auk + ", script:");
    logger.debug (script);
      }
      if (script == null) {
    throw new CannotExecuteException ("No script.");
      }
      Context cx = Context.enter();
      Scriptable scope = prepareScope (cx, activity, formPars, map);
      Reader sr = new StringReader (script);
      cx.evaluateReader (scope, sr, "<script>", 1, null);
      result.set(convertResult(cx, scope, formPars));
  } catch (AbandonedException e) {
      // thrown when the activity was deliberatly abandoned.
      result.set (new ExceptionResult (e.getMessage ()));
  } catch (RemoteException e) {
      throw e;
  } catch (IOException e) {
      logger.error (e.getMessage(), e);
      throw new CannotExecuteException (e.getMessage());
        } catch (SAXException e) {
            logger.error (e.getMessage(), e);
            throw new CannotExecuteException (e.getMessage());
  } catch (JavaScriptException e) {
      logger.error (e.getMessage(), e);
      throw new CannotExecuteException (e.getMessage());
  } finally {
      Context.exit();
      if (logger.isDebugEnabled()) {
    logger.debug ("Finished invocation of " + auk);
      }
  }
    }

    /**
     * A base class for creating context methods.
     */
    public abstract class FunctionBase
  extends ScriptableObject implements Function {

  /**
   * The class name.
   * @return the class name
   */
  public String getClassName() {
      return "FunctionBase";
  }
 
  /**
   * Instances cannot be called as constructors.
   * @param cx the context
   * @param scope the scope
   * @param args the arguments
   * @return never returns a result
   * @throws JavaScriptException if an error occurs
   */
  public Scriptable construct (Context cx, Scriptable scope,
             Object[] args)
      throws JavaScriptException {
      throw new EvaluatorException
    ("Function cannot be used as constructor");
  }
    };

    /**
     * Prepare an environment for evaluating the script.
     * @param cx the context.
     * @param activity the activity.
     * @param formPars the formal parameter definitons.
     * @param map the actual parameter values.
     * @return
     * @throws JavaScriptException
     */
    private Scriptable prepareScope
  (Context cx, Activity activity, FormalParameter[] formPars, Map map)
        throws RemoteException {
  Scriptable scope = cx.initStandardObjects(null);
  ScriptableObject wfe = new ScriptableObject () {
    public String getClassName() {
        return "ToolAgentContext";
    }
      };
  Function fo = new FunctionBase () {
    public Object call (Context cx, Scriptable scope,
            Scriptable thisObj, Object[] args)
        throws JavaScriptException {
        if (logger.isDebugEnabled ()) {
      logger.debug ("abandon called with: " + args[0]);
        }
        throw new AbandonedException ((String)args[0]);
    }
      };
  wfe.defineProperty ("abandon", fo, ScriptableObject.PERMANENT);
        wfe.defineProperty
            ("activityUniqueKey", Context.javaToJS(activity.uniqueKey(), scope),
             ScriptableObject.PERMANENT | ScriptableObject.READONLY);
  scope.put("scriptingContext", scope, wfe);
       
  prepareArguments(cx, scope, formPars, map);
  return scope;
    }

    /**
     * @param cx the context.
     * @param scope the scope.
     * @param formPars the formal parameter definitons.
     * @param map the actual parameter values.
     */
    protected void prepareArguments
        (Context cx, Scriptable scope, FormalParameter[] formPars, Map map) {
        ScriptableObject args = new ScriptableObject () {
          public String getClassName() {
              return "Arguments";
          }
            };
        for (int i = 0; i < formPars.length; i++) {
            String fp = formPars[i].id();
            args.defineProperty
          (fp,
                 convertArgument(cx, scope, formPars[i].type(), map.get(fp)),
           formPars[i].mode() == FormalParameter.Mode.IN
           ? ScriptableObject.PERMANENT | ScriptableObject.READONLY
           : ScriptableObject.PERMANENT);
        }
        scope.put("args", scope, args);
    }

    /**
     * Additionally convert SchemaType arguments
     * @see JSExecutor#convertArgument
     */
    protected Object convertArgument
        (Context cx, Scriptable scope, Object argType, Object argument) {
        if (argType instanceof ExternalReference) {
            if (XPDLUtil.isJavaType((ExternalReference)argType)) {
                return Context.javaToJS(argument, scope);
            }
        }
        return argument;
    }

    /**
     * Convert the result.
     * @param cx the context.
     * @param scope the scope.
     * @param formPars the formal parameter definitons.
     * @return
     * @throws JavaScriptException
     * @throws SAXException
     */
    protected Map convertResult
  (Context cx, Scriptable scope, FormalParameter[] fps)
  throws JavaScriptException, SAXException {
  Scriptable sres = (Scriptable)scope.get ("args", scope);
  Map resData = new HashMap ();
  for (int i = 0; i < fps.length; i++) {
      if (fps[i].mode() == FormalParameter.Mode.IN) {
    continue;
      }
      String fpn = fps[i].id();
      Object v = sres.get(fpn, sres);
      v = convertResultValue(cx, scope, fps[i], v);
      resData.put (fpn, v);
  }
  return resData;
    }

    /**
     * @param cx the context.
     * @param scope the scope.
     * @param formPars the formal parameter definitons.
     * @param value
     * @return
     * @throws JavaScriptException
     * @throws SAXException
     */
    protected Object convertResultValue
        (Context cx, Scriptable scope, FormalParameter fp, Object value)
        throws JavaScriptException, SAXException {
        if (fp.type() instanceof ExternalReference) {
            if (XPDLUtil.isJavaType((ExternalReference)fp.type())) {
                try {
                    return Context.jsToJava
                        (value,
                         XPDLUtil.getJavaType((ExternalReference)fp.type()));
                } catch (ClassNotFoundException e) {
                    (new IllegalArgumentException(e.getMessage())).initCause(e);
                }
            }
        }
        if (fp.type() == Long.class && (value instanceof Double)) {
            return new Long (((Double)value).longValue());
        }
        if (value instanceof Wrapper) {
            return ((Wrapper)value).unwrap ();
        }
        if (value instanceof XMLObject) {
            SAXEventBufferImpl seb = new SAXEventBufferImpl();
            if (((XMLObject)value).getClassName().equals("XMLList")) {
                seb.startDocument();
                for (int i = 0; true; i++) {
                    Object item = ((XMLObject)value).get(i, (XMLObject)value);
                    if (item.equals(Scriptable.NOT_FOUND)) {
                        break;
                    }
                    xmlObjectToSax(seb, (XMLObject)item, true);
                }
                seb.endDocument();
            } else {
                xmlObjectToSax(seb, (XMLObject)value, false);
            }
            seb.pack();
            return seb;
        }
        if ((value instanceof Scriptable)
                && ((Scriptable)value).getClassName().equals ("Date")) {
            Scriptable s = (Scriptable)value;
            Object gt = Scriptable.NOT_FOUND;
            for (Scriptable c = s;
                c != null && gt.equals(Scriptable.NOT_FOUND);
                c = c.getPrototype()) {
                gt = c.get("getTime", s);
            }
            Number millis = (Number)((Function)gt)
                .call(cx, scope, s, new Object[] {});
            return new Date (millis.longValue());
        }
        return value;
    }

    /**
     * Serialize a JavaScript XML object into the SAX event buffer.
     *
     * @param seb the SAX event buffer
     * @param xmlObj the XML object
     * @param fragment if <code>startDocument</code> and
     * <code>endDocument</code> events are to be suppressed
     * @throws SAXException
     */
    private void xmlObjectToSax
        (SAXEventBufferImpl seb, XMLObject xmlObj, boolean fragment)
        throws SAXException {
        Wrapper wrap = (Wrapper) ScriptableObject.callMethod
            ((XMLObject)xmlObj, "getXmlObject", new Object[0]);
        XmlObject result = (XmlObject) wrap.unwrap();
        XmlCursor cursor = result.newCursor();
        result = cursor.getObject();
        ContentHandler ch = new NamespaceAttributesFilter(seb);
        if (fragment) {
            ch = new BodyFilter (ch);
        }
        result.save(ch, seb, (new XmlOptions()).setSaveOuter());
    }

    /* Comment copied from interface. */
    public Object result () {
  Object res = result.get();
  result.set (null);
  return res;
    }

    /**
     * Describe <code>terminate</code> method here.
     *
     * @param activity a <code>WfActivity</code> value
     * @throws ApplicationNotStoppedException if the application could
     * not be terminated.
     */
    public void terminate(Activity activity)
  throws ApplicationNotStoppedException {
  throw new ApplicationNotStoppedException
      ("Terminate not implemented for JSExecutor.");
    }
}
TOP

Related Classes of de.danet.an.workflow.tools.rhino.JSExecutor$FunctionBase

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.