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