blob: 134d3fe59fe0fa96df65cae6457c324ad5e867a4 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}