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 import org.springframework.core.io.FileSystemResource;
039 import org.springframework.core.io.Resource;
040 import org.springframework.core.io.UrlResource;
041
042 /**
043 * A builder class for creating {@link Processor}, {@link Expression} and
044 * {@link Predicate} objects using the JSR 223 scripting engine.
045 *
046 * @version $Revision: 700514 $
047 */
048 public class ScriptBuilder<E extends Exchange> implements Expression<E>, Predicate<E>, Processor {
049 private static final transient Log LOG = LogFactory.getLog(ScriptBuilder.class);
050
051 private String scriptEngineName;
052 private Resource scriptResource;
053 private String scriptText;
054 private ScriptEngine engine;
055 private CompiledScript compiledScript;
056
057 public ScriptBuilder(String scriptEngineName) {
058 this.scriptEngineName = scriptEngineName;
059 }
060
061 public ScriptBuilder(String scriptEngineName, String scriptText) {
062 this(scriptEngineName);
063 this.scriptText = scriptText;
064 }
065
066 public ScriptBuilder(String scriptEngineName, Resource scriptResource) {
067 this(scriptEngineName);
068 this.scriptResource = scriptResource;
069 }
070
071 @Override
072 public String toString() {
073 return getScriptDescription();
074 }
075
076 public Object evaluate(E exchange) {
077 return evaluateScript(exchange);
078 }
079
080 public boolean matches(E exchange) {
081 Object scriptValue = evaluateScript(exchange);
082 return matches(exchange, scriptValue);
083 }
084
085 public void assertMatches(String text, E exchange) throws AssertionError {
086 Object scriptValue = evaluateScript(exchange);
087 if (!matches(exchange, scriptValue)) {
088 throw new AssertionError(this + " failed on " + exchange + " as script returned <" + scriptValue + ">");
089 }
090 }
091
092 public void process(Exchange exchange) {
093 evaluateScript(exchange);
094 }
095
096 // Builder API
097 // -------------------------------------------------------------------------
098
099 /**
100 * Sets the attribute on the context so that it is available to the script
101 * as a variable in the {@link ScriptContext#ENGINE_SCOPE}
102 *
103 * @param name the name of the attribute
104 * @param value the attribute value
105 * @return this builder
106 */
107 public ScriptBuilder attribute(String name, Object value) {
108 getScriptContext().setAttribute(name, value, ScriptContext.ENGINE_SCOPE);
109 return this;
110 }
111
112 // Create any scripting language builder recognised by JSR 223
113 // -------------------------------------------------------------------------
114
115 /**
116 * Creates a script builder for the named language and script contents
117 *
118 * @param language the language to use for the script
119 * @param scriptText the script text to be evaluted
120 * @return the builder
121 */
122 public static ScriptBuilder script(String language, String scriptText) {
123 return new ScriptBuilder(language, scriptText);
124 }
125
126 /**
127 * Creates a script builder for the named language and script {@link Resource}
128 *
129 * @param language the language to use for the script
130 * @param scriptResource the resource used to load the script
131 * @return the builder
132 */
133 public static ScriptBuilder script(String language, Resource scriptResource) {
134 return new ScriptBuilder(language, scriptResource);
135 }
136
137 /**
138 * Creates a script builder for the named language and script {@link File}
139 *
140 * @param language the language to use for the script
141 * @param scriptFile the file used to load the script
142 * @return the builder
143 */
144 public static ScriptBuilder script(String language, File scriptFile) {
145 return new ScriptBuilder(language, new FileSystemResource(scriptFile));
146 }
147
148 /**
149 * Creates a script builder for the named language and script {@link URL}
150 *
151 * @param language the language to use for the script
152 * @param scriptURL the URL used to load the script
153 * @return the builder
154 */
155 public static ScriptBuilder script(String language, URL scriptURL) {
156 return new ScriptBuilder(language, new UrlResource(scriptURL));
157 }
158
159 // Beanshell
160 // -------------------------------------------------------------------------
161
162 /**
163 * Creates a script builder for the BeanShell script contents
164 *
165 * @param scriptText the script text to be evaluted
166 * @return the builder
167 * @deprecated will be removed in Camel 2.0
168 */
169 public static ScriptBuilder beanShell(String scriptText) {
170 return new ScriptBuilder("beanshell", scriptText);
171 }
172
173 /**
174 * Creates a script builder for the BeanShell script {@link Resource}
175 *
176 * @param scriptResource the resource used to load the script
177 * @return the builder
178 * @deprecated will be removed in Camel 2.0
179 */
180 public static ScriptBuilder beanShell(Resource scriptResource) {
181 return new ScriptBuilder("beanshell", scriptResource);
182 }
183
184 /**
185 * Creates a script builder for the BeanShell script {@link File}
186 *
187 * @param scriptFile the file used to load the script
188 * @return the builder
189 * @deprecated will be removed in Camel 2.0
190 */
191 public static ScriptBuilder beanShell(File scriptFile) {
192 return new ScriptBuilder("beanshell", new FileSystemResource(scriptFile));
193 }
194
195 /**
196 * Creates a script builder for the BeanShell script {@link URL}
197 *
198 * @param scriptURL the URL used to load the script
199 * @return the builder
200 * @deprecated will be removed in Camel 2.0
201 */
202 public static ScriptBuilder beanShell(URL scriptURL) {
203 return new ScriptBuilder("beanshell", new UrlResource(scriptURL));
204 }
205
206 // Groovy
207 // -------------------------------------------------------------------------
208
209 /**
210 * Creates a script builder for the groovy script contents
211 *
212 * @param scriptText the script text to be evaluted
213 * @return the builder
214 */
215 public static ScriptBuilder groovy(String scriptText) {
216 return new ScriptBuilder("groovy", scriptText);
217 }
218
219 /**
220 * Creates a script builder for the groovy script {@link Resource}
221 *
222 * @param scriptResource the resource used to load the script
223 * @return the builder
224 */
225 public static ScriptBuilder groovy(Resource scriptResource) {
226 return new ScriptBuilder("groovy", scriptResource);
227 }
228
229 /**
230 * Creates a script builder for the groovy script {@link File}
231 *
232 * @param scriptFile the file used to load the script
233 * @return the builder
234 */
235 public static ScriptBuilder groovy(File scriptFile) {
236 return new ScriptBuilder("groovy", new FileSystemResource(scriptFile));
237 }
238
239 /**
240 * Creates a script builder for the groovy script {@link URL}
241 *
242 * @param scriptURL the URL used to load the script
243 * @return the builder
244 */
245 public static ScriptBuilder groovy(URL scriptURL) {
246 return new ScriptBuilder("groovy", new UrlResource(scriptURL));
247 }
248
249 // JavaScript
250 // -------------------------------------------------------------------------
251
252 /**
253 * Creates a script builder for the JavaScript/ECMAScript script contents
254 *
255 * @param scriptText the script text to be evaluted
256 * @return the builder
257 */
258 public static ScriptBuilder javaScript(String scriptText) {
259 return new ScriptBuilder("js", scriptText);
260 }
261
262 /**
263 * Creates a script builder for the JavaScript/ECMAScript script
264 *
265 * @{link Resource}
266 * @param scriptResource the resource used to load the script
267 * @return the builder
268 */
269 public static ScriptBuilder javaScript(Resource scriptResource) {
270 return new ScriptBuilder("js", scriptResource);
271 }
272
273 /**
274 * Creates a script builder for the JavaScript/ECMAScript script {@link File}
275 *
276 * @param scriptFile the file used to load the script
277 * @return the builder
278 */
279 public static ScriptBuilder javaScript(File scriptFile) {
280 return new ScriptBuilder("js", new FileSystemResource(scriptFile));
281 }
282
283 /**
284 * Creates a script builder for the JavaScript/ECMAScript script {@link URL}
285 *
286 * @param scriptURL the URL used to load the script
287 * @return the builder
288 */
289 public static ScriptBuilder javaScript(URL scriptURL) {
290 return new ScriptBuilder("js", new UrlResource(scriptURL));
291 }
292
293 // PHP
294 // -------------------------------------------------------------------------
295
296 /**
297 * Creates a script builder for the PHP script contents
298 *
299 * @param scriptText the script text to be evaluted
300 * @return the builder
301 */
302 public static ScriptBuilder php(String scriptText) {
303 return new ScriptBuilder("php", scriptText);
304 }
305
306 /**
307 * Creates a script builder for the PHP script {@link Resource}
308 *
309 * @param scriptResource the resource used to load the script
310 * @return the builder
311 */
312 public static ScriptBuilder php(Resource scriptResource) {
313 return new ScriptBuilder("php", scriptResource);
314 }
315
316 /**
317 * Creates a script builder for the PHP script {@link File}
318 *
319 * @param scriptFile the file used to load the script
320 * @return the builder
321 */
322 public static ScriptBuilder php(File scriptFile) {
323 return new ScriptBuilder("php", new FileSystemResource(scriptFile));
324 }
325
326 /**
327 * Creates a script builder for the PHP script {@link URL}
328 *
329 * @param scriptURL the URL used to load the script
330 * @return the builder
331 */
332 public static ScriptBuilder php(URL scriptURL) {
333 return new ScriptBuilder("php", new UrlResource(scriptURL));
334 }
335
336 // Python
337 // -------------------------------------------------------------------------
338
339 /**
340 * Creates a script builder for the Python script contents
341 *
342 * @param scriptText the script text to be evaluted
343 * @return the builder
344 */
345 public static ScriptBuilder python(String scriptText) {
346 return new ScriptBuilder("python", scriptText);
347 }
348
349 /**
350 * Creates a script builder for the Python script {@link Resource}
351 *
352 * @param scriptResource the resource used to load the script
353 * @return the builder
354 */
355 public static ScriptBuilder python(Resource scriptResource) {
356 return new ScriptBuilder("python", scriptResource);
357 }
358
359 /**
360 * Creates a script builder for the Python script {@link File}
361 *
362 * @param scriptFile the file used to load the script
363 * @return the builder
364 */
365 public static ScriptBuilder python(File scriptFile) {
366 return new ScriptBuilder("python", new FileSystemResource(scriptFile));
367 }
368
369 /**
370 * Creates a script builder for the Python script {@link URL}
371 *
372 * @param scriptURL the URL used to load the script
373 * @return the builder
374 */
375 public static ScriptBuilder python(URL scriptURL) {
376 return new ScriptBuilder("python", new UrlResource(scriptURL));
377 }
378
379 // Ruby/JRuby
380 // -------------------------------------------------------------------------
381
382 /**
383 * Creates a script builder for the Ruby/JRuby script contents
384 *
385 * @param scriptText the script text to be evaluted
386 * @return the builder
387 */
388 public static ScriptBuilder ruby(String scriptText) {
389 return new ScriptBuilder("jruby", scriptText);
390 }
391
392 /**
393 * Creates a script builder for the Ruby/JRuby script {@link Resource}
394 *
395 * @param scriptResource the resource used to load the script
396 * @return the builder
397 */
398 public static ScriptBuilder ruby(Resource scriptResource) {
399 return new ScriptBuilder("jruby", scriptResource);
400 }
401
402 /**
403 * Creates a script builder for the Ruby/JRuby script {@link File}
404 *
405 * @param scriptFile the file used to load the script
406 * @return the builder
407 */
408 public static ScriptBuilder ruby(File scriptFile) {
409 return new ScriptBuilder("jruby", new FileSystemResource(scriptFile));
410 }
411
412 /**
413 * Creates a script builder for the Ruby/JRuby script {@link URL}
414 *
415 * @param scriptURL the URL used to load the script
416 * @return the builder
417 */
418 public static ScriptBuilder ruby(URL scriptURL) {
419 return new ScriptBuilder("jruby", new UrlResource(scriptURL));
420 }
421
422 // Properties
423 // -------------------------------------------------------------------------
424 public ScriptEngine getEngine() {
425 checkInitialised();
426 if (engine == null) {
427 throw new IllegalArgumentException("No script engine could be created for: " + getScriptEngineName());
428 }
429 return engine;
430 }
431
432 public CompiledScript getCompiledScript() {
433 return compiledScript;
434 }
435
436 public String getScriptText() {
437 return scriptText;
438 }
439
440 public void setScriptText(String scriptText) {
441 this.scriptText = scriptText;
442 }
443
444 public String getScriptEngineName() {
445 return scriptEngineName;
446 }
447
448 /**
449 * Returns a description of the script
450 *
451 * @return the script description
452 */
453 public String getScriptDescription() {
454 if (scriptText != null) {
455 return scriptEngineName + ": " + scriptText;
456 } else if (scriptResource != null) {
457 return scriptEngineName + ": " + scriptResource.getDescription();
458 } else {
459 return scriptEngineName + ": null script";
460 }
461 }
462
463 /**
464 * Access the script context so that it can be configured such as adding
465 * attributes
466 */
467 public ScriptContext getScriptContext() {
468 return getEngine().getContext();
469 }
470
471 /**
472 * Sets the context to use by the script
473 */
474 public void setScriptContext(ScriptContext scriptContext) {
475 getEngine().setContext(scriptContext);
476 }
477
478 public Resource getScriptResource() {
479 return scriptResource;
480 }
481
482 public void setScriptResource(Resource scriptResource) {
483 this.scriptResource = scriptResource;
484 }
485
486 // Implementation methods
487 // -------------------------------------------------------------------------
488 protected void checkInitialised() {
489 if (scriptText == null && scriptResource == null) {
490 throw new IllegalArgumentException("Neither scriptText or scriptResource are specified");
491 }
492 if (engine == null) {
493 engine = createScriptEngine();
494 }
495 if (compiledScript == null) {
496 if (engine instanceof Compilable) {
497 compileScript((Compilable)engine);
498 }
499 }
500 }
501
502 protected boolean matches(E exchange, Object scriptValue) {
503 return ObjectConverter.toBool(scriptValue);
504 }
505
506 protected ScriptEngine createScriptEngine() {
507 ScriptEngineManager manager = new ScriptEngineManager();
508 try {
509 engine = manager.getEngineByName(scriptEngineName);
510 } catch (NoClassDefFoundError ex) {
511 LOG.error("Can't load the scriptEngine for " + scriptEngineName + ", the exception is " + ex
512 + ", please check the scriptEngine needs jars.");
513 }
514 if (engine == null) {
515 throw new IllegalArgumentException("No script engine could be created for: " + getScriptEngineName());
516 }
517 if (isPython()) {
518 ScriptContext context = engine.getContext();
519 context.setAttribute("com.sun.script.jython.comp.mode", "eval", ScriptContext.ENGINE_SCOPE);
520 }
521 return engine;
522 }
523
524 protected void compileScript(Compilable compilable) {
525 try {
526 if (scriptText != null) {
527 compiledScript = compilable.compile(scriptText);
528 } else if (scriptResource != null) {
529 compiledScript = compilable.compile(createScriptReader());
530 }
531 } catch (ScriptException e) {
532 if (LOG.isDebugEnabled()) {
533 LOG.debug("Script compile failed: " + e, e);
534 }
535 throw createScriptCompileException(e);
536 } catch (IOException e) {
537 throw createScriptCompileException(e);
538 }
539 }
540
541 protected synchronized Object evaluateScript(Exchange exchange) {
542 try {
543 getScriptContext();
544 populateBindings(getEngine(), exchange);
545 Object result = runScript();
546 if (LOG.isDebugEnabled()) {
547 LOG.debug("The script evaluation result is: " + result);
548 }
549 return result;
550 } catch (ScriptException e) {
551 if (LOG.isDebugEnabled()) {
552 LOG.debug("Script evaluation failed: " + e, e);
553 }
554 throw createScriptEvaluationException(e.getCause());
555 } catch (IOException e) {
556 throw createScriptEvaluationException(e);
557 }
558 }
559
560 protected Object runScript() throws ScriptException, IOException {
561 checkInitialised();
562 Object result = null;
563 if (compiledScript != null) {
564 result = compiledScript.eval();
565 } else {
566 if (scriptText != null) {
567 result = getEngine().eval(scriptText);
568 } else {
569 result = getEngine().eval(createScriptReader());
570 }
571 }
572 return result;
573 }
574
575 protected void populateBindings(ScriptEngine engine, Exchange exchange) {
576 ScriptContext context = engine.getContext();
577 int scope = ScriptContext.ENGINE_SCOPE;
578 context.setAttribute("context", exchange.getContext(), scope);
579 context.setAttribute("exchange", exchange, scope);
580 context.setAttribute("request", exchange.getIn(), scope);
581 context.setAttribute("response", exchange.getOut(false), scope);
582 }
583
584 protected InputStreamReader createScriptReader() throws IOException {
585 // TODO consider character sets?
586 return new InputStreamReader(scriptResource.getInputStream());
587 }
588
589 protected ScriptEvaluationException createScriptCompileException(Exception e) {
590 return new ScriptEvaluationException("Failed to compile: " + getScriptDescription() + ". Cause: " + e, e);
591 }
592
593 protected ScriptEvaluationException createScriptEvaluationException(Throwable e) {
594 if (e.getClass().getName().equals("org.jruby.exceptions.RaiseException")) {
595 // Only the nested exception has the specific problem
596 try {
597 Object ex = e.getClass().getMethod("getException").invoke(e);
598 return new ScriptEvaluationException("Failed to evaluate: " + getScriptDescription() + ". Error: " + ex + ". Cause: " + e, e);
599 } catch (Exception e1) {
600 // do nothing here
601 }
602 }
603 return new ScriptEvaluationException("Failed to evaluate: " + getScriptDescription() + ". Cause: " + e, e);
604 }
605
606 protected boolean isPython() {
607 return "python".equals(scriptEngineName) || "jython".equals(scriptEngineName);
608 }
609
610 }