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