View Javadoc

1   /*
2    * $Id: Scriptable.java 11884 2008-06-02 10:44:30Z rossmason $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.module.scripting.component;
12  
13  import org.mule.MuleServer;
14  import org.mule.DefaultMuleEventContext;
15  import org.mule.transport.NullPayload;
16  import org.mule.api.MuleEvent;
17  import org.mule.api.lifecycle.Initialisable;
18  import org.mule.api.lifecycle.InitialisationException;
19  import org.mule.api.transformer.TransformerException;
20  import org.mule.api.transport.MessageAdapter;
21  import org.mule.config.i18n.CoreMessages;
22  import org.mule.config.i18n.MessageFactory;
23  import org.mule.util.CollectionUtils;
24  import org.mule.util.IOUtils;
25  import org.mule.util.StringUtils;
26  
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.InputStreamReader;
30  import java.io.Reader;
31  import java.io.StringReader;
32  import java.util.Iterator;
33  import java.util.Map;
34  import java.util.Properties;
35  
36  import javax.script.Bindings;
37  import javax.script.Compilable;
38  import javax.script.CompiledScript;
39  import javax.script.ScriptEngine;
40  import javax.script.ScriptEngineManager;
41  import javax.script.ScriptException;
42  
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  
46  /**
47   * A JSR 223 Script service. Allows any JSR 223 compliant script engines such as
48   * JavaScript, Groovy or Rhino to be embedded as Mule components.
49   */
50  public class Scriptable implements Initialisable
51  {
52      /** The actual body of the script */
53      private String scriptText;
54      
55      /** A file from which the script will be loaded */
56      private String scriptFile;
57      
58      /** Parameters to be made available to the script as variables */
59      private Properties properties;
60  
61      /** The name of the JSR 223 scripting engine (e.g., "groovy") */
62      private String scriptEngineName;
63  
64      /////////////////////////////////////////////////////////////////////////////
65      // Internal variables, not exposed as properties
66      /////////////////////////////////////////////////////////////////////////////
67  
68      /** A compiled version of the script, if the scripting engine supports it */
69      private CompiledScript compiledScript;
70      
71      private ScriptEngine scriptEngine;
72      private ScriptEngineManager scriptEngineManager;
73      
74      protected transient Log logger = LogFactory.getLog(getClass());
75      
76      public void initialise() throws InitialisationException
77      {
78          scriptEngineManager = new ScriptEngineManager();
79  
80          // Create scripting engine
81          if (scriptEngineName != null)
82          {
83              scriptEngine = createScriptEngineByName(scriptEngineName);
84              if (scriptEngine == null)
85              {
86                  throw new InitialisationException(MessageFactory.createStaticMessage("Scripting engine '" + scriptEngineName + "' not found.  Available engines are: " + listAvailableEngines()), this);
87              }
88          }
89          // Determine scripting engine to use by file extension
90          else if (scriptFile != null)
91          {
92              int i = scriptFile.lastIndexOf(".");
93              if (i > -1)
94              {
95                  logger.info("Script Engine name not set. Guessing by file extension.");
96                  String ext = scriptFile.substring(i + 1);
97                  scriptEngine = createScriptEngineByExtension(ext);
98                  if (scriptEngine == null)
99                  {
100                     throw new InitialisationException(MessageFactory.createStaticMessage("File extension '" + ext + "' does not map to a scripting engine.  Available engines are: " + listAvailableEngines()), this);
101                 }
102                 else
103                 {
104                     setScriptEngineName(scriptEngine.getFactory().getEngineName());
105                 }
106             }
107         }
108 
109         Reader script;
110         // Load script from variable
111         if (StringUtils.isNotBlank(scriptText))
112         {
113             script = new StringReader(scriptText);
114         }
115         // Load script from file
116         else if (scriptFile != null)
117         {
118             InputStream is;
119             try
120             {
121                 is = IOUtils.getResourceAsStream(scriptFile, getClass());
122             }
123             catch (IOException e)
124             {
125                 throw new InitialisationException(CoreMessages.cannotLoadFromClasspath(scriptFile), e, this);
126             }
127             if (is == null)
128             {
129                 throw new InitialisationException(CoreMessages.cannotLoadFromClasspath(scriptFile), this);
130             }
131             script = new InputStreamReader(is);
132         }
133         else
134         {
135             throw new InitialisationException(CoreMessages.propertiesNotSet("scriptText, scriptFile"), this);
136         }
137 
138         // Pre-compile script if scripting engine supports compilation.
139         if (scriptEngine instanceof Compilable)
140         {
141             try
142             {
143                 compiledScript = ((Compilable) scriptEngine).compile(script);
144             }
145             catch (ScriptException e)
146             {
147                 throw new InitialisationException(e, this);
148             }
149         }
150 
151 
152     }
153 
154     public void populateDefaultBindings(Bindings bindings)
155     {
156         if (properties != null)
157         {
158             bindings.putAll((Map) properties);
159         }
160         bindings.put("log", logger);
161         //A place holder for a retuen result if the script doesn't return a result.
162         //The script can overwrite this binding
163         bindings.put("result", NullPayload.getInstance());
164         bindings.put("muleContext", MuleServer.getMuleContext());
165     }
166 
167     public void populateBindings(Bindings bindings, Object payload)
168     {
169         populateDefaultBindings(bindings);
170         bindings.put("payload", payload);
171         //For backward compatability. Usually used by the script transformer since
172         //src maps with the argument passed into the transformer
173         bindings.put("src", payload);
174     }
175     
176     public void populateBindings(Bindings bindings, MessageAdapter message)
177     {
178         populateDefaultBindings(bindings);
179         bindings.put("message", message);
180         //This will get overwritten if populateBindings(Bindings bindings, MuleEvent event) is called
181         //and not this method directly.
182         bindings.put("payload", message.getPayload());
183         //For backward compatability
184         bindings.put("src", message.getPayload());
185 
186         // Set any message properties as variables for the script.
187         String propertyName;
188         for (Iterator iterator = message.getPropertyNames().iterator(); iterator.hasNext();)
189         {
190             propertyName = (String)iterator.next();
191             bindings.put(propertyName, message.getProperty(propertyName));
192         }
193     }
194 
195     public void populateBindings(Bindings bindings, MuleEvent event)
196     {
197         populateBindings(bindings, event.getMessage());
198         bindings.put("originalPayload", event.getMessage().getPayload());
199 
200         try
201         {
202             bindings.put("payload", event.transformMessage());
203         }
204         catch (TransformerException e)
205         {
206             logger.warn(e);
207         }
208         
209         bindings.put("eventContext", new DefaultMuleEventContext(event));
210         bindings.put("id", event.getId());
211         bindings.put("service", event.getService());
212     }
213     
214     public Object runScript(Bindings bindings) throws ScriptException
215     {
216         Object result;
217         if (compiledScript != null)
218         {
219             result = compiledScript.eval(bindings);
220         }
221         else
222         {
223             result = scriptEngine.eval(scriptText, bindings);
224         }
225         
226         // The result of the script can be returned directly or it can
227         // be set as the variable "result".
228         if (result == null)
229         {
230             result = bindings.get("result");
231         }
232         return result;
233     }
234 
235     protected ScriptEngine createScriptEngineByName(String name)
236     {
237         return scriptEngineManager.getEngineByName(name);
238     }
239 
240     protected ScriptEngine createScriptEngineByExtension(String ext)
241     {
242         return scriptEngineManager.getEngineByExtension(ext);
243     }
244 
245     protected String listAvailableEngines()
246     {
247         return CollectionUtils.toString(scriptEngineManager.getEngineFactories(), false);
248     }
249 
250     ////////////////////////////////////////////////////////////////////////////////
251     // Getters and setters
252     ////////////////////////////////////////////////////////////////////////////////
253 
254     public String getScriptText()
255     {
256         return scriptText;
257     }
258 
259     public void setScriptText(String scriptText)
260     {
261         this.scriptText = scriptText;
262     }
263 
264     public String getScriptFile()
265     {
266         return scriptFile;
267     }
268 
269     public void setScriptFile(String scriptFile)
270     {
271         this.scriptFile = scriptFile;
272     }
273 
274     public void setScriptEngineName(String scriptEngineName)
275     {
276         this.scriptEngineName = scriptEngineName;
277     }
278 
279     public String getScriptEngineName()
280     {
281         return scriptEngineName;
282     }
283     
284     public Properties getProperties()
285     {
286         return properties;
287     }
288 
289     public void setProperties(Properties properties)
290     {
291         this.properties = properties;
292     }
293     
294     public ScriptEngine getScriptEngine()
295     {
296         return scriptEngine;
297     }
298 
299     protected void setScriptEngine(ScriptEngine scriptEngine)
300     {
301         this.scriptEngine = scriptEngine;
302     }
303 
304     protected CompiledScript getCompiledScript()
305     {
306         return compiledScript;
307     }
308 
309     protected void setCompiledScript(CompiledScript compiledScript)
310     {
311         this.compiledScript = compiledScript;
312     }
313 }