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 }