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: 769448 $
047 */
048 public class ScriptBuilder implements Expression, Predicate, 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(Exchange exchange) {
077 return evaluateScript(exchange);
078 }
079
080 public <T> T evaluate(Exchange exchange, Class<T> type) {
081 Object result = evaluate(exchange);
082 return exchange.getContext().getTypeConverter().convertTo(type, result);
083 }
084
085 public boolean matches(Exchange exchange) {
086 Object scriptValue = evaluateScript(exchange);
087 return matches(exchange, scriptValue);
088 }
089
090 public void assertMatches(String text, Exchange exchange) throws AssertionError {
091 Object scriptValue = evaluateScript(exchange);
092 if (!matches(exchange, scriptValue)) {
093 throw new AssertionError(this + " failed on " + exchange + " as script returned <" + scriptValue + ">");
094 }
095 }
096
097 public void process(Exchange exchange) {
098 evaluateScript(exchange);
099 }
100
101 // Builder API
102 // -------------------------------------------------------------------------
103
104 /**
105 * Sets the attribute on the context so that it is available to the script
106 * as a variable in the {@link ScriptContext#ENGINE_SCOPE}
107 *
108 * @param name the name of the attribute
109 * @param value the attribute value
110 * @return this builder
111 */
112 public ScriptBuilder attribute(String name, Object value) {
113 getScriptContext().setAttribute(name, value, ScriptContext.ENGINE_SCOPE);
114 return this;
115 }
116
117 // Create any scripting language builder recognised by JSR 223
118 // -------------------------------------------------------------------------
119
120 /**
121 * Creates a script builder for the named language and script contents
122 *
123 * @param language the language to use for the script
124 * @param scriptText the script text to be evaluated
125 * @return the builder
126 */
127 public static ScriptBuilder script(String language, String scriptText) {
128 return new ScriptBuilder(language, scriptText);
129 }
130
131 /**
132 * Creates a script builder for the named language and script {@link Resource}
133 *
134 * @param language the language to use for the script
135 * @param scriptResource the resource used to load the script
136 * @return the builder
137 */
138 public static ScriptBuilder script(String language, Resource scriptResource) {
139 return new ScriptBuilder(language, scriptResource);
140 }
141
142 /**
143 * Creates a script builder for the named language and script {@link File}
144 *
145 * @param language the language to use for the script
146 * @param scriptFile the file used to load the script
147 * @return the builder
148 */
149 public static ScriptBuilder script(String language, File scriptFile) {
150 return new ScriptBuilder(language, new FileSystemResource(scriptFile));
151 }
152
153 /**
154 * Creates a script builder for the named language and script {@link URL}
155 *
156 * @param language the language to use for the script
157 * @param scriptURL the URL used to load the script
158 * @return the builder
159 */
160 public static ScriptBuilder script(String language, URL scriptURL) {
161 return new ScriptBuilder(language, new UrlResource(scriptURL));
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 evaluated
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 // JavaScript
208 // -------------------------------------------------------------------------
209
210 /**
211 * Creates a script builder for the JavaScript/ECMAScript script contents
212 *
213 * @param scriptText the script text to be evaluated
214 * @return the builder
215 */
216 public static ScriptBuilder javaScript(String scriptText) {
217 return new ScriptBuilder("js", scriptText);
218 }
219
220 /**
221 * Creates a script builder for the JavaScript/ECMAScript script
222 *
223 * @{link Resource}
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 // PHP
252 // -------------------------------------------------------------------------
253
254 /**
255 * Creates a script builder for the PHP script contents
256 *
257 * @param scriptText the script text to be evaluated
258 * @return the builder
259 */
260 public static ScriptBuilder php(String scriptText) {
261 return new ScriptBuilder("php", scriptText);
262 }
263
264 /**
265 * Creates a script builder for the PHP script {@link Resource}
266 *
267 * @param scriptResource the resource used to load the script
268 * @return the builder
269 */
270 public static ScriptBuilder php(Resource scriptResource) {
271 return new ScriptBuilder("php", scriptResource);
272 }
273
274 /**
275 * Creates a script builder for the PHP script {@link File}
276 *
277 * @param scriptFile the file used to load the script
278 * @return the builder
279 */
280 public static ScriptBuilder php(File scriptFile) {
281 return new ScriptBuilder("php", new FileSystemResource(scriptFile));
282 }
283
284 /**
285 * Creates a script builder for the PHP script {@link URL}
286 *
287 * @param scriptURL the URL used to load the script
288 * @return the builder
289 */
290 public static ScriptBuilder php(URL scriptURL) {
291 return new ScriptBuilder("php", new UrlResource(scriptURL));
292 }
293
294 // Python
295 // -------------------------------------------------------------------------
296
297 /**
298 * Creates a script builder for the Python script contents
299 *
300 * @param scriptText the script text to be evaluated
301 * @return the builder
302 */
303 public static ScriptBuilder python(String scriptText) {
304 return new ScriptBuilder("python", scriptText);
305 }
306
307 /**
308 * Creates a script builder for the Python script {@link Resource}
309 *
310 * @param scriptResource the resource used to load the script
311 * @return the builder
312 */
313 public static ScriptBuilder python(Resource scriptResource) {
314 return new ScriptBuilder("python", scriptResource);
315 }
316
317 /**
318 * Creates a script builder for the Python script {@link File}
319 *
320 * @param scriptFile the file used to load the script
321 * @return the builder
322 */
323 public static ScriptBuilder python(File scriptFile) {
324 return new ScriptBuilder("python", new FileSystemResource(scriptFile));
325 }
326
327 /**
328 * Creates a script builder for the Python script {@link URL}
329 *
330 * @param scriptURL the URL used to load the script
331 * @return the builder
332 */
333 public static ScriptBuilder python(URL scriptURL) {
334 return new ScriptBuilder("python", new UrlResource(scriptURL));
335 }
336
337 // Ruby/JRuby
338 // -------------------------------------------------------------------------
339
340 /**
341 * Creates a script builder for the Ruby/JRuby script contents
342 *
343 * @param scriptText the script text to be evaluated
344 * @return the builder
345 */
346 public static ScriptBuilder ruby(String scriptText) {
347 return new ScriptBuilder("jruby", scriptText);
348 }
349
350 /**
351 * Creates a script builder for the Ruby/JRuby script {@link Resource}
352 *
353 * @param scriptResource the resource used to load the script
354 * @return the builder
355 */
356 public static ScriptBuilder ruby(Resource scriptResource) {
357 return new ScriptBuilder("jruby", scriptResource);
358 }
359
360 /**
361 * Creates a script builder for the Ruby/JRuby script {@link File}
362 *
363 * @param scriptFile the file used to load the script
364 * @return the builder
365 */
366 public static ScriptBuilder ruby(File scriptFile) {
367 return new ScriptBuilder("jruby", new FileSystemResource(scriptFile));
368 }
369
370 /**
371 * Creates a script builder for the Ruby/JRuby script {@link URL}
372 *
373 * @param scriptURL the URL used to load the script
374 * @return the builder
375 */
376 public static ScriptBuilder ruby(URL scriptURL) {
377 return new ScriptBuilder("jruby", new UrlResource(scriptURL));
378 }
379
380 // Properties
381 // -------------------------------------------------------------------------
382 public ScriptEngine getEngine() {
383 checkInitialised();
384 if (engine == null) {
385 throw new IllegalArgumentException("No script engine could be created for: " + getScriptEngineName());
386 }
387 return engine;
388 }
389
390 public CompiledScript getCompiledScript() {
391 return compiledScript;
392 }
393
394 public String getScriptText() {
395 return scriptText;
396 }
397
398 public void setScriptText(String scriptText) {
399 this.scriptText = scriptText;
400 }
401
402 public String getScriptEngineName() {
403 return scriptEngineName;
404 }
405
406 /**
407 * Returns a description of the script
408 *
409 * @return the script description
410 */
411 public String getScriptDescription() {
412 if (scriptText != null) {
413 return scriptEngineName + ": " + scriptText;
414 } else if (scriptResource != null) {
415 return scriptEngineName + ": " + scriptResource.getDescription();
416 } else {
417 return scriptEngineName + ": null script";
418 }
419 }
420
421 /**
422 * Access the script context so that it can be configured such as adding
423 * attributes
424 */
425 public ScriptContext getScriptContext() {
426 return getEngine().getContext();
427 }
428
429 /**
430 * Sets the context to use by the script
431 */
432 public void setScriptContext(ScriptContext scriptContext) {
433 getEngine().setContext(scriptContext);
434 }
435
436 public Resource getScriptResource() {
437 return scriptResource;
438 }
439
440 public void setScriptResource(Resource scriptResource) {
441 this.scriptResource = scriptResource;
442 }
443
444 // Implementation methods
445 // -------------------------------------------------------------------------
446 protected void checkInitialised() {
447 if (scriptText == null && scriptResource == null) {
448 throw new IllegalArgumentException("Neither scriptText or scriptResource are specified");
449 }
450 if (engine == null) {
451 engine = createScriptEngine();
452 }
453 if (compiledScript == null) {
454 if (engine instanceof Compilable) {
455 compileScript((Compilable)engine);
456 }
457 }
458 }
459
460 protected boolean matches(Exchange exchange, Object scriptValue) {
461 return ObjectConverter.toBool(scriptValue);
462 }
463
464 protected ScriptEngine createScriptEngine() {
465 ScriptEngineManager manager = new ScriptEngineManager();
466 try {
467 engine = manager.getEngineByName(scriptEngineName);
468 } catch (NoClassDefFoundError ex) {
469 LOG.error("Can't load the scriptEngine for " + scriptEngineName + ", the exception is " + ex
470 + ", please check the scriptEngine needs jars.");
471 }
472 if (engine == null) {
473 throw new IllegalArgumentException("No script engine could be created for: " + getScriptEngineName());
474 }
475 if (isPython()) {
476 ScriptContext context = engine.getContext();
477 context.setAttribute("com.sun.script.jython.comp.mode", "eval", ScriptContext.ENGINE_SCOPE);
478 }
479 return engine;
480 }
481
482 protected void compileScript(Compilable compilable) {
483 try {
484 if (scriptText != null) {
485 compiledScript = compilable.compile(scriptText);
486 } else if (scriptResource != null) {
487 compiledScript = compilable.compile(createScriptReader());
488 }
489 } catch (ScriptException e) {
490 if (LOG.isDebugEnabled()) {
491 LOG.debug("Script compile failed: " + e, e);
492 }
493 throw createScriptCompileException(e);
494 } catch (IOException e) {
495 throw createScriptCompileException(e);
496 }
497 }
498
499 protected synchronized Object evaluateScript(Exchange exchange) {
500 try {
501 getScriptContext();
502 populateBindings(getEngine(), exchange);
503 Object result = runScript();
504 if (LOG.isDebugEnabled()) {
505 LOG.debug("The script evaluation result is: " + result);
506 }
507 return result;
508 } catch (ScriptException e) {
509 if (LOG.isDebugEnabled()) {
510 LOG.debug("Script evaluation failed: " + e, e);
511 }
512 throw createScriptEvaluationException(e.getCause());
513 } catch (IOException e) {
514 throw createScriptEvaluationException(e);
515 }
516 }
517
518 protected Object runScript() throws ScriptException, IOException {
519 checkInitialised();
520 Object result = null;
521 if (compiledScript != null) {
522 result = compiledScript.eval();
523 } else {
524 if (scriptText != null) {
525 result = getEngine().eval(scriptText);
526 } else {
527 result = getEngine().eval(createScriptReader());
528 }
529 }
530 return result;
531 }
532
533 protected void populateBindings(ScriptEngine engine, Exchange exchange) {
534 ScriptContext context = engine.getContext();
535 int scope = ScriptContext.ENGINE_SCOPE;
536 context.setAttribute("context", exchange.getContext(), scope);
537 context.setAttribute("exchange", exchange, scope);
538 context.setAttribute("request", exchange.getIn(), scope);
539 if (exchange.hasOut()) {
540 context.setAttribute("response", exchange.getOut(), scope);
541 }
542 }
543
544 protected InputStreamReader createScriptReader() throws IOException {
545 // TODO consider character sets?
546 return new InputStreamReader(scriptResource.getInputStream());
547 }
548
549 protected ScriptEvaluationException createScriptCompileException(Exception e) {
550 return new ScriptEvaluationException("Failed to compile: " + getScriptDescription() + ". Cause: " + e, e);
551 }
552
553 protected ScriptEvaluationException createScriptEvaluationException(Throwable e) {
554 if (e.getClass().getName().equals("org.jruby.exceptions.RaiseException")) {
555 // Only the nested exception has the specific problem
556 try {
557 Object ex = e.getClass().getMethod("getException").invoke(e);
558 return new ScriptEvaluationException("Failed to evaluate: " + getScriptDescription() + ". Error: " + ex + ". Cause: " + e, e);
559 } catch (Exception e1) {
560 // do nothing here
561 }
562 }
563 return new ScriptEvaluationException("Failed to evaluate: " + getScriptDescription() + ". Cause: " + e, e);
564 }
565
566 protected boolean isPython() {
567 return "python".equals(scriptEngineName) || "jython".equals(scriptEngineName);
568 }
569
570 }