001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.builder.script;
018    
019    import java.io.File;
020    import java.io.IOException;
021    import java.io.InputStreamReader;
022    import java.net.URL;
023    
024    import javax.script.Compilable;
025    import javax.script.CompiledScript;
026    import javax.script.ScriptContext;
027    import javax.script.ScriptEngine;
028    import javax.script.ScriptEngineManager;
029    import javax.script.ScriptException;
030    
031    import org.apache.camel.Exchange;
032    import org.apache.camel.Expression;
033    import org.apache.camel.Predicate;
034    import org.apache.camel.Processor;
035    import org.apache.camel.converter.ObjectConverter;
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    
039    import org.springframework.core.io.FileSystemResource;
040    import org.springframework.core.io.Resource;
041    import org.springframework.core.io.UrlResource;
042    
043    /**
044     * A builder class for creating {@link Processor}, {@link Expression} and
045     * {@link Predicate} objects using the JSR 223 scripting engine.
046     * 
047     * @version $Revision: 563665 $
048     */
049    public class ScriptBuilder<E extends Exchange> implements Expression<E>, Predicate<E>, Processor {
050        private static final transient Log LOG = LogFactory.getLog(ScriptBuilder.class);
051    
052        private String scriptEngineName;
053        private Resource scriptResource;
054        private String scriptText;
055        private ScriptEngine engine;
056        private CompiledScript compiledScript;
057    
058        public ScriptBuilder(String scriptEngineName) {
059            this.scriptEngineName = scriptEngineName;
060        }
061    
062        public ScriptBuilder(String scriptEngineName, String scriptText) {
063            this(scriptEngineName);
064            this.scriptText = scriptText;
065        }
066    
067        public ScriptBuilder(String scriptEngineName, Resource scriptResource) {
068            this(scriptEngineName);
069            this.scriptResource = scriptResource;
070        }
071    
072        @Override
073        public String toString() {
074            return getScriptDescription();
075        }
076    
077        public Object evaluate(E exchange) {
078            return evaluateScript(exchange);
079        }
080    
081        public boolean matches(E exchange) {
082            Object scriptValue = evaluateScript(exchange);
083            return matches(exchange, scriptValue);
084        }
085    
086        public void assertMatches(String text, E exchange) throws AssertionError {
087            Object scriptValue = evaluateScript(exchange);
088            if (!matches(exchange, scriptValue)) {
089                throw new AssertionError(this + " failed on " + exchange + " as script returned <" + scriptValue + ">");
090            }
091        }
092    
093        public void process(Exchange exchange) {
094            evaluateScript(exchange);
095        }
096    
097        // Builder API
098        // -------------------------------------------------------------------------
099    
100        /**
101         * Sets the attribute on the context so that it is available to the script
102         * as a variable in the {@link ScriptContext#ENGINE_SCOPE}
103         * 
104         * @param name the name of the attribute
105         * @param value the attribute value
106         * @return this builder
107         */
108        public ScriptBuilder attribute(String name, Object value) {
109            getScriptContext().setAttribute(name, value, ScriptContext.ENGINE_SCOPE);
110            return this;
111        }
112    
113        // Create any scripting language builder recognised by JSR 223
114        // -------------------------------------------------------------------------
115    
116        /**
117         * Creates a script builder for the named language and script contents
118         * 
119         * @param language the language to use for the script
120         * @param scriptText the script text to be evaluted
121         * @return the builder
122         */
123        public static ScriptBuilder script(String language, String scriptText) {
124            return new ScriptBuilder(language, scriptText);
125        }
126    
127        /**
128         * Creates a script builder for the named language and script
129         * 
130         * @{link Resource}
131         * @param language the language to use for the script
132         * @param scriptResource the resource used to load the script
133         * @return the builder
134         */
135        public static ScriptBuilder script(String language, Resource scriptResource) {
136            return new ScriptBuilder(language, scriptResource);
137        }
138    
139        /**
140         * Creates a script builder for the named language and script
141         * 
142         * @{link File}
143         * @param language the language to use for the script
144         * @param scriptFile the file used to load the script
145         * @return the builder
146         */
147        public static ScriptBuilder script(String language, File scriptFile) {
148            return new ScriptBuilder(language, new FileSystemResource(scriptFile));
149        }
150    
151        /**
152         * Creates a script builder for the named language and script
153         * 
154         * @{link URL}
155         * @param language the language to use for the script
156         * @param scriptURL the URL used to load the script
157         * @return the builder
158         */
159        public static ScriptBuilder script(String language, URL scriptURL) {
160            return new ScriptBuilder(language, new UrlResource(scriptURL));
161        }
162    
163        // Groovy
164        // -------------------------------------------------------------------------
165    
166        /**
167         * Creates a script builder for the groovy script contents
168         * 
169         * @param scriptText the script text to be evaluted
170         * @return the builder
171         */
172        public static ScriptBuilder groovy(String scriptText) {
173            return new ScriptBuilder("groovy", scriptText);
174        }
175    
176        /**
177         * Creates a script builder for the groovy script
178         * 
179         * @{link Resource}
180         * @param scriptResource the resource used to load the script
181         * @return the builder
182         */
183        public static ScriptBuilder groovy(Resource scriptResource) {
184            return new ScriptBuilder("groovy", scriptResource);
185        }
186    
187        /**
188         * Creates a script builder for the groovy script
189         * 
190         * @{link File}
191         * @param scriptFile the file used to load the script
192         * @return the builder
193         */
194        public static ScriptBuilder groovy(File scriptFile) {
195            return new ScriptBuilder("groovy", new FileSystemResource(scriptFile));
196        }
197    
198        /**
199         * Creates a script builder for the groovy script
200         * 
201         * @{link URL}
202         * @param scriptURL the URL used to load the script
203         * @return the builder
204         */
205        public static ScriptBuilder groovy(URL scriptURL) {
206            return new ScriptBuilder("groovy", new UrlResource(scriptURL));
207        }
208    
209        // JavaScript
210        // -------------------------------------------------------------------------
211    
212        /**
213         * Creates a script builder for the JavaScript/ECMAScript script contents
214         * 
215         * @param scriptText the script text to be evaluted
216         * @return the builder
217         */
218        public static ScriptBuilder javaScript(String scriptText) {
219            return new ScriptBuilder("js", scriptText);
220        }
221    
222        /**
223         * Creates a script builder for the JavaScript/ECMAScript script
224         * 
225         * @{link Resource}
226         * @param scriptResource the resource used to load the script
227         * @return the builder
228         */
229        public static ScriptBuilder javaScript(Resource scriptResource) {
230            return new ScriptBuilder("js", scriptResource);
231        }
232    
233        /**
234         * Creates a script builder for the JavaScript/ECMAScript script
235         * 
236         * @{link File}
237         * @param scriptFile the file used to load the script
238         * @return the builder
239         */
240        public static ScriptBuilder javaScript(File scriptFile) {
241            return new ScriptBuilder("js", new FileSystemResource(scriptFile));
242        }
243    
244        /**
245         * Creates a script builder for the JavaScript/ECMAScript script
246         * 
247         * @{link URL}
248         * @param scriptURL the URL used to load the script
249         * @return the builder
250         */
251        public static ScriptBuilder javaScript(URL scriptURL) {
252            return new ScriptBuilder("js", new UrlResource(scriptURL));
253        }
254    
255        // PHP
256        // -------------------------------------------------------------------------
257    
258        /**
259         * Creates a script builder for the PHP script contents
260         * 
261         * @param scriptText the script text to be evaluted
262         * @return the builder
263         */
264        public static ScriptBuilder php(String scriptText) {
265            return new ScriptBuilder("php", scriptText);
266        }
267    
268        /**
269         * Creates a script builder for the PHP script
270         * 
271         * @{link Resource}
272         * @param scriptResource the resource used to load the script
273         * @return the builder
274         */
275        public static ScriptBuilder php(Resource scriptResource) {
276            return new ScriptBuilder("php", scriptResource);
277        }
278    
279        /**
280         * Creates a script builder for the PHP script
281         * 
282         * @{link File}
283         * @param scriptFile the file used to load the script
284         * @return the builder
285         */
286        public static ScriptBuilder php(File scriptFile) {
287            return new ScriptBuilder("php", new FileSystemResource(scriptFile));
288        }
289    
290        /**
291         * Creates a script builder for the PHP script
292         * 
293         * @{link URL}
294         * @param scriptURL the URL used to load the script
295         * @return the builder
296         */
297        public static ScriptBuilder php(URL scriptURL) {
298            return new ScriptBuilder("php", new UrlResource(scriptURL));
299        }
300    
301        // Python
302        // -------------------------------------------------------------------------
303    
304        /**
305         * Creates a script builder for the Python script contents
306         * 
307         * @param scriptText the script text to be evaluted
308         * @return the builder
309         */
310        public static ScriptBuilder python(String scriptText) {
311            return new ScriptBuilder("python", scriptText);
312        }
313    
314        /**
315         * Creates a script builder for the Python script
316         * 
317         * @{link Resource}
318         * @param scriptResource the resource used to load the script
319         * @return the builder
320         */
321        public static ScriptBuilder python(Resource scriptResource) {
322            return new ScriptBuilder("python", scriptResource);
323        }
324    
325        /**
326         * Creates a script builder for the Python script
327         * 
328         * @{link File}
329         * @param scriptFile the file used to load the script
330         * @return the builder
331         */
332        public static ScriptBuilder python(File scriptFile) {
333            return new ScriptBuilder("python", new FileSystemResource(scriptFile));
334        }
335    
336        /**
337         * Creates a script builder for the Python script
338         * 
339         * @{link URL}
340         * @param scriptURL the URL used to load the script
341         * @return the builder
342         */
343        public static ScriptBuilder python(URL scriptURL) {
344            return new ScriptBuilder("python", new UrlResource(scriptURL));
345        }
346    
347        // Ruby/JRuby
348        // -------------------------------------------------------------------------
349    
350        /**
351         * Creates a script builder for the Ruby/JRuby script contents
352         * 
353         * @param scriptText the script text to be evaluted
354         * @return the builder
355         */
356        public static ScriptBuilder ruby(String scriptText) {
357            return new ScriptBuilder("jruby", scriptText);
358        }
359    
360        /**
361         * Creates a script builder for the Ruby/JRuby script
362         * 
363         * @{link Resource}
364         * @param scriptResource the resource used to load the script
365         * @return the builder
366         */
367        public static ScriptBuilder ruby(Resource scriptResource) {
368            return new ScriptBuilder("jruby", scriptResource);
369        }
370    
371        /**
372         * Creates a script builder for the Ruby/JRuby script
373         * 
374         * @{link File}
375         * @param scriptFile the file used to load the script
376         * @return the builder
377         */
378        public static ScriptBuilder ruby(File scriptFile) {
379            return new ScriptBuilder("jruby", new FileSystemResource(scriptFile));
380        }
381    
382        /**
383         * Creates a script builder for the Ruby/JRuby script
384         * 
385         * @{link URL}
386         * @param scriptURL the URL used to load the script
387         * @return the builder
388         */
389        public static ScriptBuilder ruby(URL scriptURL) {
390            return new ScriptBuilder("jruby", new UrlResource(scriptURL));
391        }
392    
393        // Properties
394        // -------------------------------------------------------------------------
395        public ScriptEngine getEngine() {
396            checkInitialised();
397            return engine;
398        }
399    
400        public CompiledScript getCompiledScript() {
401            return compiledScript;
402        }
403    
404        public String getScriptText() {
405            return scriptText;
406        }
407    
408        public void setScriptText(String scriptText) {
409            this.scriptText = scriptText;
410        }
411    
412        public String getScriptEngineName() {
413            return scriptEngineName;
414        }
415    
416        /**
417         * Returns a description of the script
418         * 
419         * @return the script description
420         */
421        public String getScriptDescription() {
422            if (scriptText != null) {
423                return scriptEngineName + ": " + scriptText;
424            } else if (scriptResource != null) {
425                return scriptEngineName + ": " + scriptResource.getDescription();
426            } else {
427                return scriptEngineName + ": null script";
428            }
429        }
430    
431        /**
432         * Access the script context so that it can be configured such as adding
433         * attributes
434         */
435        public ScriptContext getScriptContext() {
436            return getEngine().getContext();
437        }
438    
439        /**
440         * Sets the context to use by the script
441         */
442        public void setScriptContext(ScriptContext scriptContext) {
443            getEngine().setContext(scriptContext);
444        }
445    
446        public Resource getScriptResource() {
447            return scriptResource;
448        }
449    
450        public void setScriptResource(Resource scriptResource) {
451            this.scriptResource = scriptResource;
452        }
453    
454        // Implementation methods
455        // -------------------------------------------------------------------------
456        protected void checkInitialised() {
457            if (scriptText == null && scriptResource == null) {
458                throw new IllegalArgumentException("Neither scriptText or scriptResource are specified");
459            }
460            if (engine == null) {
461                engine = createScriptEngine();
462            }
463            if (compiledScript == null) {
464                if (engine instanceof Compilable) {
465                    compileScript((Compilable)engine);
466                }
467            }
468        }
469    
470        protected boolean matches(E exchange, Object scriptValue) {
471            return ObjectConverter.toBoolean(scriptValue);
472        }
473    
474        protected ScriptEngine createScriptEngine() {
475            ScriptEngineManager manager = new ScriptEngineManager();
476            return manager.getEngineByName(scriptEngineName);
477        }
478    
479        protected void compileScript(Compilable compilable) {
480            try {
481                if (scriptText != null) {
482                    compiledScript = compilable.compile(scriptText);
483                } else if (scriptResource != null) {
484                    compiledScript = compilable.compile(createScriptReader());
485                }
486            } catch (ScriptException e) {
487                if (LOG.isDebugEnabled()) {
488                    LOG.debug("Script compile failed: " + e, e);
489                }
490                throw createScriptCompileException(e);
491            } catch (IOException e) {
492                throw createScriptCompileException(e);
493            }
494        }
495    
496        protected synchronized Object evaluateScript(Exchange exchange) {
497            try {
498                getScriptContext();
499                populateBindings(getEngine(), exchange);
500                return runScript();
501            } catch (ScriptException e) {
502                if (LOG.isDebugEnabled()) {
503                    LOG.debug("Script evaluation failed: " + e, e);
504                }
505                throw createScriptEvaluationException(e.getCause());
506            } catch (IOException e) {
507                throw createScriptEvaluationException(e);
508            }
509        }
510    
511        protected Object runScript() throws ScriptException, IOException {
512            checkInitialised();
513            if (compiledScript != null) {
514                return compiledScript.eval();
515            } else {
516                if (scriptText != null) {
517                    return getEngine().eval(scriptText);
518                } else {
519                    return getEngine().eval(createScriptReader());
520                }
521            }
522        }
523    
524        protected void populateBindings(ScriptEngine engine, Exchange exchange) {
525            ScriptContext context = engine.getContext();
526            int scope = ScriptContext.ENGINE_SCOPE;
527            context.setAttribute("context", exchange.getContext(), scope);
528            context.setAttribute("exchange", exchange, scope);
529            context.setAttribute("request", exchange.getIn(), scope);
530            context.setAttribute("response", exchange.getOut(), scope);
531        }
532    
533        protected InputStreamReader createScriptReader() throws IOException {
534            // TODO consider character sets?
535            return new InputStreamReader(scriptResource.getInputStream());
536        }
537    
538        protected ScriptEvaluationException createScriptCompileException(Exception e) {
539            return new ScriptEvaluationException("Failed to compile: " + getScriptDescription() + ". Cause: " + e, e);
540        }
541    
542        protected ScriptEvaluationException createScriptEvaluationException(Throwable e) {
543            return new ScriptEvaluationException("Failed to evaluate: " + getScriptDescription() + ". Cause: " + e, e);
544        }
545    
546    }