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 }