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 }