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 }