net.sf.taverna.t2.activities.beanshell.BeanshellActivity.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.taverna.t2.activities.beanshell.BeanshellActivity.java

Source

/*******************************************************************************
 * Copyright (C) 2007 The University of Manchester
 *
 *  Modifications to the initial code base are copyright of their
 *  respective authors, or their employers as appropriate.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser 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
 ******************************************************************************/
package net.sf.taverna.t2.activities.beanshell;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import net.sf.taverna.t2.activities.dependencyactivity.AbstractAsynchronousDependencyActivity;
import net.sf.taverna.t2.reference.ErrorDocumentService;
import net.sf.taverna.t2.reference.ReferenceService;
import net.sf.taverna.t2.reference.ReferenceServiceException;
import net.sf.taverna.t2.reference.T2Reference;
import net.sf.taverna.t2.workflowmodel.OutputPort;
import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityInputPort;
import net.sf.taverna.t2.workflowmodel.processor.activity.AsynchronousActivityCallback;

import org.apache.log4j.Logger;

import uk.org.taverna.configuration.app.ApplicationConfiguration;
import bsh.EvalError;
import bsh.Interpreter;
import bsh.TargetError;

import com.fasterxml.jackson.databind.JsonNode;

/**
 * An Activity providing Beanshell functionality.
 * 
 * @author David Withers
 * @author Stuart Owen
 * @author Alex Nenadic
 */
public class BeanshellActivity extends AbstractAsynchronousDependencyActivity {

    public static final String URI = "http://ns.taverna.org.uk/2010/activity/beanshell";

    protected BeanshellActivityConfigurationBean configurationBean;

    private static Logger logger = Logger.getLogger(BeanshellActivity.class);

    private Interpreter interpreter;

    private static String CLEAR_COMMAND = "clear();";

    private JsonNode json;

    public BeanshellActivity(ApplicationConfiguration applicationConfiguration) {
        super(applicationConfiguration);
        createInterpreter();
    }

    @Override
    public JsonNode getConfiguration() {
        return json;
    }

    @Override
    public void configure(JsonNode json) {
        this.json = json;
        checkGranularDepths();
    }

    /**
     * Creates the interpreter required to run the beanshell script, and assigns
     * the correct classloader setting according to the
     */
    private void createInterpreter() {
        interpreter = new Interpreter();
    }

    /**
     * As the Beanshell activity currently only can output values at the
     * specified depth, the granular depths should always be equal to the actual
     * depth.
     * <p>
     * Workflow definitions created with Taverna 2.0b1 would not honour this and
     * always set the granular depth to 0.
     * <p>
     * This method modifies the granular depths to be equal to the depths.
     */
    protected void checkGranularDepths() {
        for (OutputPort outputPort : getOutputPorts()) {
            if (outputPort.getGranularDepth() != outputPort.getDepth()) {
                logger.warn("Replacing granular depth of port " + outputPort.getName());
                // outputPort.setGranularDepth(outputPort.getDepth());
            }
        }
    }

    public ActivityInputPort getInputPort(String name) {
        for (ActivityInputPort port : getInputPorts()) {
            if (port.getName().equals(name)) {
                return port;
            }
        }
        return null;
    }

    private void clearInterpreter() {
        try {
            interpreter.eval(CLEAR_COMMAND);
        } catch (EvalError e) {
            logger.error("Could not clear the interpreter", e);
        }
    }

    @Override
    public void executeAsynch(final Map<String, T2Reference> data, final AsynchronousActivityCallback callback) {
        callback.requestRun(new Runnable() {

            public void run() {

                // Workflow run identifier (needed when classloader sharing is
                // set to 'workflow').
                String procID = callback.getParentProcessIdentifier();
                String workflowRunID;
                if (procID.contains(":")) {
                    workflowRunID = procID.substring(0, procID.indexOf(':'));
                } else {
                    workflowRunID = procID; // for tests, will be an empty
                    // string
                }

                synchronized (interpreter) {

                    // Configure the classloader for executing the Beanshell
                    if (classLoader == null) {
                        try {
                            classLoader = findClassLoader(json, workflowRunID);
                            interpreter.setClassLoader(classLoader);
                        } catch (RuntimeException rex) {
                            String message = "Unable to obtain the classloader for Beanshell service";
                            callback.fail(message, rex);
                            return;
                        }
                    }

                    ReferenceService referenceService = callback.getContext().getReferenceService();

                    Map<String, T2Reference> outputData = new HashMap<String, T2Reference>();

                    clearInterpreter();
                    try {
                        // set inputs
                        for (String inputName : data.keySet()) {
                            ActivityInputPort inputPort = getInputPort(inputName);
                            Object input = referenceService.renderIdentifier(data.get(inputName),
                                    inputPort.getTranslatedElementClass(), callback.getContext());
                            inputName = sanatisePortName(inputName);
                            interpreter.set(inputName, input);
                        }
                        // run
                        interpreter.eval(json.get("script").asText());
                        // get outputs
                        for (OutputPort outputPort : getOutputPorts()) {
                            String name = outputPort.getName();
                            Object value = interpreter.get(name);
                            if (value == null) {
                                ErrorDocumentService errorDocService = referenceService.getErrorDocumentService();
                                value = errorDocService.registerError(
                                        "No value produced for output variable " + name, outputPort.getDepth(),
                                        callback.getContext());
                            }
                            outputData.put(name, referenceService.register(value, outputPort.getDepth(), true,
                                    callback.getContext()));
                        }
                        callback.receiveResult(outputData, new int[0]);
                    } catch (EvalError e) {
                        logger.error(e);
                        try {
                            int lineNumber = e.getErrorLineNumber();

                            callback.fail("Line " + lineNumber + ": " + determineMessage(e));
                        } catch (NullPointerException e2) {
                            callback.fail(determineMessage(e));
                        }
                    } catch (ReferenceServiceException e) {
                        callback.fail("Error accessing beanshell input/output data for " + this, e);
                    }
                    clearInterpreter();
                }
            }

            /**
             * Removes any invalid characters from the port name. For example,
             * xml-text would become xmltext.
             * 
             * @param name
             * @return
             */
            private String sanatisePortName(String name) {
                String result = name;
                if (Pattern.matches("\\w++", name) == false) {
                    result = "";
                    for (char c : name.toCharArray()) {
                        if (Character.isLetterOrDigit(c) || c == '_') {
                            result += c;
                        }
                    }
                }
                return result;
            }
        });

    }

    private static String determineMessage(Throwable e) {
        if (e instanceof TargetError) {
            Throwable t = ((TargetError) e).getTarget();
            if (t != null) {
                return t.getClass().getCanonicalName() + ": " + determineMessage(t);
            }
        }
        Throwable cause = e.getCause();
        if (cause != null) {
            return determineMessage(cause);
        }
        return e.getMessage();
    }
}