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 }