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 }