org.mule.module.scripting.component.Scriptable.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.module.scripting.component.Scriptable.java

Source

/*
 * $Id$
 * --------------------------------------------------------------------------------------
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.module.scripting.component;

import org.mule.DefaultMuleEventContext;
import org.mule.DefaultMuleMessage;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleMessage;
import org.mule.api.context.MuleContextAware;
import org.mule.api.lifecycle.Initialisable;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.service.Service;
import org.mule.api.transport.PropertyScope;
import org.mule.config.i18n.CoreMessages;
import org.mule.config.i18n.MessageFactory;
import org.mule.transport.NullPayload;
import org.mule.util.CollectionUtils;
import org.mule.util.IOUtils;
import org.mule.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A JSR 223 Script service. Allows any JSR 223 compliant script engines such as JavaScript, Groovy or Rhino
 * to be embedded as Mule components.
 */
public class Scriptable implements Initialisable, MuleContextAware {
    /** The actual body of the script */
    private String scriptText;

    /** A file from which the script will be loaded */
    private String scriptFile;

    /** Parameters to be made available to the script as variables */
    private Properties properties;

    /** The name of the JSR 223 scripting engine (e.g., "groovy") */
    private String scriptEngineName;

    // ///////////////////////////////////////////////////////////////////////////
    // Internal variables, not exposed as properties
    // ///////////////////////////////////////////////////////////////////////////

    /** A compiled version of the script, if the scripting engine supports it */
    private CompiledScript compiledScript;

    private ScriptEngine scriptEngine;
    private ScriptEngineManager scriptEngineManager;

    private MuleContext muleContext;

    protected transient Log logger = LogFactory.getLog(getClass());

    public Scriptable() {
        // For Spring
    }

    public Scriptable(MuleContext muleContext) {
        this.muleContext = muleContext;
    }

    public void setMuleContext(MuleContext context) {
        this.muleContext = context;
    }

    public void initialise() throws InitialisationException {
        scriptEngineManager = new ScriptEngineManager();

        // Create scripting engine
        if (scriptEngineName != null) {
            scriptEngine = createScriptEngineByName(scriptEngineName);
            if (scriptEngine == null) {
                throw new InitialisationException(MessageFactory.createStaticMessage("Scripting engine '"
                        + scriptEngineName + "' not found.  Available engines are: " + listAvailableEngines()),
                        this);
            }
        }
        // Determine scripting engine to use by file extension
        else if (scriptFile != null) {
            int i = scriptFile.lastIndexOf(".");
            if (i > -1) {
                logger.info("Script Engine name not set. Guessing by file extension.");
                String ext = scriptFile.substring(i + 1);
                scriptEngine = createScriptEngineByExtension(ext);
                if (scriptEngine == null) {
                    throw new InitialisationException(MessageFactory.createStaticMessage("File extension '" + ext
                            + "' does not map to a scripting engine.  Available engines are: "
                            + listAvailableEngines()), this);
                } else {
                    setScriptEngineName(scriptEngine.getFactory().getEngineName());
                }
            }
        }

        Reader script;
        // Load script from variable
        if (StringUtils.isNotBlank(scriptText)) {
            script = new StringReader(scriptText);
        }
        // Load script from file
        else if (scriptFile != null) {
            InputStream is;
            try {
                is = IOUtils.getResourceAsStream(scriptFile, getClass());
            } catch (IOException e) {
                throw new InitialisationException(CoreMessages.cannotLoadFromClasspath(scriptFile), e, this);
            }
            if (is == null) {
                throw new InitialisationException(CoreMessages.cannotLoadFromClasspath(scriptFile), this);
            }
            script = new InputStreamReader(is);
        } else {
            throw new InitialisationException(CoreMessages.propertiesNotSet("scriptText, scriptFile"), this);
        }

        // Pre-compile script if scripting engine supports compilation.
        if (scriptEngine instanceof Compilable) {
            try {
                compiledScript = ((Compilable) scriptEngine).compile(script);
            } catch (ScriptException e) {
                throw new InitialisationException(e, this);
            }
        }
    }

    public void populateDefaultBindings(Bindings bindings) {
        if (properties != null) {
            bindings.putAll((Map) properties);
        }
        bindings.put("log", logger);
        // A place holder for a returned result if the script doesn't return a result.
        // The script can overwrite this binding
        bindings.put("result", NullPayload.getInstance());
        bindings.put("muleContext", muleContext);
        bindings.put("registry", muleContext.getRegistry());
    }

    public void populateBindings(Bindings bindings, Object payload) {
        populateDefaultBindings(bindings);
        bindings.put("payload", payload);
        // For backward compatability. Usually used by the script transformer since
        // src maps with the argument passed into the transformer
        bindings.put("src", payload);
    }

    public void populateBindings(Bindings bindings, MuleMessage message) {
        populateDefaultBindings(bindings);
        if (message == null) {
            message = new DefaultMuleMessage(NullPayload.getInstance(), muleContext);
        }

        populateVariablesInOrder(bindings, message);

        bindings.put("message", message);
        // This will get overwritten if populateBindings(Bindings bindings, MuleEvent event) is called
        // and not this method directly.
        bindings.put("payload", message.getPayload());
        // For backward compatability
        bindings.put("src", message.getPayload());

        populateHeadersVariablesAndException(bindings, message);
    }

    public void populateBindings(Bindings bindings, MuleEvent event) {
        populateBindings(bindings, event.getMessage());
        bindings.put("originalPayload", event.getMessage().getPayload());
        bindings.put("payload", event.getMessage().getPayload());
        bindings.put("eventContext", new DefaultMuleEventContext(event));
        bindings.put("id", event.getId());
        bindings.put("flowConstruct", event.getFlowConstruct());
        if (event.getFlowConstruct() instanceof Service) {
            bindings.put("service", event.getFlowConstruct());
        }
    }

    private void populateHeadersVariablesAndException(Bindings bindings, MuleMessage message) {
        bindings.put("flowVars", new MesssagePropertyMap(message, PropertyScope.INVOCATION));
        bindings.put("sessionVars", new MesssagePropertyMap(message, PropertyScope.SESSION));

        // Only add exception is present
        if (message.getExceptionPayload() != null) {
            bindings.put("exception", message.getExceptionPayload().getException());
        } else {
            bindings.put("exception", null);
        }
    }

    private void populateVariablesInOrder(Bindings bindings, MuleMessage message) {
        for (String key : message.getSessionPropertyNames()) {
            bindings.put(key, message.getSessionProperty(key));
        }
        for (String key : message.getInvocationPropertyNames()) {
            bindings.put(key, message.getInvocationProperty(key));
        }
    }

    public Object runScript(Bindings bindings) throws ScriptException {
        Object result;
        try {
            RegistryLookupBindings registryLookupBindings = new RegistryLookupBindings(muleContext.getRegistry(),
                    bindings);
            if (compiledScript != null) {
                result = compiledScript.eval(registryLookupBindings);
            } else {
                result = scriptEngine.eval(scriptText, registryLookupBindings);
            }

            // The result of the script can be returned directly or it can
            // be set as the variable "result".
            if (result == null) {
                result = registryLookupBindings.get("result");
            }
        } catch (ScriptException e) {
            // re-throw
            throw e;
        } catch (Exception ex) {
            throw new ScriptException(ex);
        }
        return result;
    }

    protected ScriptEngine createScriptEngineByName(String name) {
        return scriptEngineManager.getEngineByName(name);
    }

    protected ScriptEngine createScriptEngineByExtension(String ext) {
        return scriptEngineManager.getEngineByExtension(ext);
    }

    protected String listAvailableEngines() {
        return CollectionUtils.toString(scriptEngineManager.getEngineFactories(), false);
    }

    // //////////////////////////////////////////////////////////////////////////////
    // Getters and setters
    // //////////////////////////////////////////////////////////////////////////////

    public String getScriptText() {
        return scriptText;
    }

    public void setScriptText(String scriptText) {
        this.scriptText = scriptText;
    }

    public String getScriptFile() {
        return scriptFile;
    }

    public void setScriptFile(String scriptFile) {
        this.scriptFile = scriptFile;
    }

    public void setScriptEngineName(String scriptEngineName) {
        this.scriptEngineName = scriptEngineName;
    }

    public String getScriptEngineName() {
        return scriptEngineName;
    }

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public ScriptEngine getScriptEngine() {
        return scriptEngine;
    }

    protected void setScriptEngine(ScriptEngine scriptEngine) {
        this.scriptEngine = scriptEngine;
    }

    protected CompiledScript getCompiledScript() {
        return compiledScript;
    }

    protected void setCompiledScript(CompiledScript compiledScript) {
        this.compiledScript = compiledScript;
    }

    private static class MesssagePropertyMap implements Map<String, Object> {
        MuleMessage message;
        PropertyScope propertyScope;

        public MesssagePropertyMap(MuleMessage message, PropertyScope propertyScope) {
            this.message = message;
            this.propertyScope = propertyScope;
        }

        @Override
        public void clear() {
            message.clearProperties(propertyScope);
        }

        @Override
        public boolean containsKey(Object key) {
            return message.getPropertyNames(propertyScope).contains(key);
        }

        @Override
        public boolean containsValue(Object value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Set<java.util.Map.Entry<String, Object>> entrySet() {
            throw new UnsupportedOperationException();
        }

        @Override
        public Object get(Object key) {
            return message.getProperty((String) key, propertyScope);
        }

        @Override
        public boolean isEmpty() {
            return message.getPropertyNames(propertyScope).isEmpty();
        }

        @Override
        public Set<String> keySet() {
            return message.getPropertyNames(propertyScope);
        }

        @Override
        public Object put(String key, Object value) {
            if (PropertyScope.INBOUND.equals(propertyScope)) {
                throw new UnsupportedOperationException("Inbound message properties are read-only");
            } else {
                message.setProperty(key, value, propertyScope);
                return value;
            }
        }

        @Override
        public void putAll(Map<? extends String, ? extends Object> m) {
            for (Map.Entry<? extends String, ? extends Object> entry : m.entrySet()) {
                put(entry.getKey(), entry.getValue());
            }
        }

        @Override
        public Object remove(Object key) {
            if (PropertyScope.INBOUND.equals(propertyScope)) {
                throw new UnsupportedOperationException("Inbound message properties are read-only");
            } else {
                return message.removeProperty((String) key, propertyScope);
            }
        }

        @Override
        public int size() {
            return message.getPropertyNames(propertyScope).size();
        }

        @Override
        public Collection<Object> values() {
            throw new UnsupportedOperationException();
        }
    }

}