/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.soa.esb.actions.scripting;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import groovy.util.GroovyScriptEngine;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.util.StreamUtils;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.actions.ActionLifecycleException;
import org.jboss.soa.esb.actions.ActionPipelineProcessor;
import org.jboss.soa.esb.actions.ActionProcessingException;
import org.jboss.soa.esb.actions.ActionUtils;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.jboss.soa.esb.message.body.content.BytesBody;
import org.jboss.soa.esb.util.ClassUtil;
import java.io.IOException;
import java.io.InputStream;
/**
* <a href="http://groovy.codehaus.org">Groovy</a> Scripting action processor.
* <p/>
* <pre>
* <action name="groovy" class="org.jboss.soa.esb.actions.scripting.GroovyActionProcessor">
* <property name="script" value="/scripts/helloWorld.groovy" />
* </action>
* </pre>
* <p/>
* The {@link Message} is bound into the script with the name "message".
* The {@link ConfigTree} is bound into the script with the name "config".
* The {@link MessagePayloadProxy} is bound into the script with the name "payloadProxy".
* The {@link Logger} is bound into the script with the name "logger".
* <p/>
* The script can also be supplied to this action as the message payload, allowing you to
* dynamically supply the action script. For message based scripts to be executable,
* the "script" action property must be omitted and the "supportMessageBasedScripting" property must
* be set to "true". There are obvious security issues around executing message based scripts,
* so use this feature controlled manner.
*
* @author Gregory Pierce.
* @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a>
*/
public class GroovyActionProcessor implements ActionPipelineProcessor {
private static Logger logger = Logger.getLogger(GroovyActionProcessor.class);
protected ConfigTree configTree;
protected GroovyScriptEngine scriptEngine;
private String scriptPath;
private Script script;
private boolean cacheScript;
private MessagePayloadProxy payloadProxy;
public GroovyActionProcessor(ConfigTree config) throws ConfigurationException {
this.configTree = config;
payloadProxy = new MessagePayloadProxy(configTree,
new String[] {BytesBody.BYTES_LOCATION, ActionUtils.POST_ACTION_DATA},
new String[] {ActionUtils.POST_ACTION_DATA});
}
public void initialise() throws ActionLifecycleException {
scriptPath = configTree.getAttribute("script");
cacheScript = configTree.getBooleanAttribute("cacheScript", true);
if(scriptPath == null) {
boolean supportMessageBasedScripting = configTree.getBooleanAttribute("supportMessageBasedScripting", false);
if(supportMessageBasedScripting) {
logger.info("No Groovy script specified on action config " + configTree.getAttribute("name")
+ ". Expecting Groovy script to be in message.");
} else {
throw new ActionLifecycleException("'script' not configured on the action and message based scripting is not enabled ('supportMessageBasedScripting=false').");
}
}
}
/**
* Get the input stream for the Groovy script (from the classpath).
* @param scriptPath The script classpath.
* @return An input
*/
public static String getScriptFromClasspath(String scriptPath) throws IOException {
InputStream scriptStream;
scriptStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptPath);
if(scriptStream == null) {
scriptStream = ClassUtil.getResourceAsStream(scriptPath, GroovyActionProcessor.class);
if(scriptStream == null) {
throw new IOException("Script '" + scriptPath + "' not found on classpath.");
}
}
try {
return new String(StreamUtils.readStream(scriptStream));
} finally {
try {
if(scriptStream != null) {
scriptStream.close();
}
} catch (IOException e) {
throw new IOException("Failed to close script '" + scriptPath + "' stream.");
}
}
}
public void destroy() throws ActionLifecycleException {
}
/**
* Processes an ESB message. Configuration is via two properties of this ESB element
* scriptPath - The path to the script that will be run
* script - the name of the script that will be run (include .groovy exception)
*/
public synchronized Message process(Message message) throws ActionProcessingException {
try {
Script execScript = getScript(message);
// create a Java/Groovy binding for the message object
//
Binding binding = new Binding();
// binds the message object to the script with a variable name of 'message'
//
binding.setVariable("message", message);
binding.setVariable("config", configTree);
binding.setVariable("payloadProxy", payloadProxy);
binding.setVariable("logger", logger);
execScript.setBinding(binding);
Object returnVal = execScript.run();
if(returnVal instanceof Message) {
return (Message) returnVal;
} else {
return message;
}
}
catch (Exception e) {
logger.error("Error executing Groovy script.", e);
throw new ActionProcessingException("Error executing Groovy script.", e);
}
}
protected Script getScript(Message message) throws ActionProcessingException {
if(scriptPath != null) {
if(script == null || !cacheScript) {
try {
String scriptText = GroovyActionProcessor.getScriptFromClasspath(scriptPath);
script = constructScriptInstance(scriptText);
} catch (IOException e) {
throw new ActionProcessingException("Error reading script '" + scriptPath + "' stream.");
}
}
return script;
} else {
// So, the script is being passed in in the message...
Object messageScript;
try {
messageScript = payloadProxy.getPayload(message);
} catch (MessageDeliverException e) {
throw new ActionProcessingException(e);
}
if(messageScript instanceof String) {
return constructScriptInstance((String)messageScript);
} else if(messageScript instanceof byte[]) {
return constructScriptInstance(new String((byte[])messageScript));
} else {
throw new ActionProcessingException("Groovy script not specified in message.");
}
}
}
private Script constructScriptInstance(String scriptText) {
GroovyShell shell = new GroovyShell(Thread.currentThread().getContextClassLoader());
return shell.parse(scriptText);
}
public void processException(final Message message, final Throwable th) {
}
public void processSuccess(final Message message) {
}
}