1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.synapse.mediators.bsf; 21 22 import org.apache.axiom.om.OMElement; 23 import org.apache.axiom.om.OMText; 24 import org.apache.bsf.xml.XMLHelper; 25 import org.apache.synapse.MessageContext; 26 import org.apache.synapse.SynapseException; 27 import org.apache.synapse.config.Entry; 28 import org.apache.synapse.mediators.AbstractMediator; 29 30 import javax.script; 31 import javax.activation.DataHandler; 32 import java.util.TreeMap; 33 import java.util.Map; 34 import java.util.Iterator; 35 import java.io.BufferedReader; 36 import java.io.InputStreamReader; 37 import java.io.IOException; 38 39 /** 40 * A Synapse mediator that calls a function in any scripting language supported by the BSF. 41 * The ScriptMediator supports scripts specified in-line or those loaded through a registry 42 * <p/> 43 * <pre> 44 * <script [key="entry-key"] 45 * [function="script-function-name"] language="javascript|groovy|ruby"> 46 * (text | xml)? 47 * </script> 48 * </pre> 49 * <p/> 50 * <p/> 51 * The function is an optional attribute defining the name of the script function to call, 52 * if not specified it defaults to a function named 'mediate'. The function takes a single 53 * parameter which is the Synapse MessageContext. The function may return a boolean, if it 54 * does not then true is assumed. 55 */ 56 public class ScriptMediator extends AbstractMediator { 57 58 /** 59 * The name of the variable made available to the scripting language to access the message 60 */ 61 private static final String MC_VAR_NAME = "mc"; 62 63 /** 64 * The registry entry key for a script loaded from the registry 65 */ 66 private String key; 67 /** 68 * The language of the script code 69 */ 70 private String language; 71 /** 72 * The map of included scripts; key = registry entry key, value = script source 73 */ 74 private Map includes = new TreeMap(); 75 /** 76 * The optional name of the function to be invoked, defaults to mediate 77 */ 78 private String function = "mediate"; 79 /** 80 * The source code of the script 81 */ 82 private String scriptSourceCode; 83 /** 84 * The BSF engine created to process each message through the script 85 */ 86 protected ScriptEngine scriptEngine; 87 /** 88 * Does the ScriptEngine support multi-threading 89 */ 90 private boolean multiThreadedEngine; 91 /** 92 * The compiled script. Only used for inline scripts 93 */ 94 private CompiledScript compiledScript; 95 /** 96 * The Invocable script. Only used for external scripts 97 */ 98 private Invocable invocableScript; 99 /** 100 * The BSF helper to convert between the XML representations used by Java and the scripting language 101 */ 102 private XMLHelper xmlHelper; 103 104 /** Lock used to ensure thread-safe lookup of the object from the registry */ 105 private final Object resourceLock = new Object(); 106 107 /** 108 * Create a script mediator for the given language and given script source 109 * 110 * @param language the BSF language 111 * @param scriptSourceCode the source code of the script 112 */ 113 public ScriptMediator(String language, String scriptSourceCode) { 114 this.language = language; 115 this.scriptSourceCode = scriptSourceCode; 116 initInlineScript(); 117 } 118 119 /** 120 * Create a script mediator for the given language and given script entry key and function 121 * 122 * @param language the BSF language 123 * @param key the registry entry key to load the script 124 * @param function the function to be invoked 125 */ 126 public ScriptMediator(String language, Map includeKeysMap, String key, String function) { 127 this.language = language; 128 this.key = key; 129 this.includes = includeKeysMap; 130 if (function != null) { 131 this.function = function; 132 } 133 134 initScriptEngine(); 135 if (!(scriptEngine instanceof Invocable)) { 136 throw new SynapseException("Script engine is not an Invocable engine for language: " + language); 137 } 138 invocableScript = (Invocable) scriptEngine; 139 } 140 141 /** 142 * Perform Script mediation 143 * 144 * @param synCtx the Synapse message context 145 * @return the boolean result from the script invocation 146 */ 147 public boolean mediate(MessageContext synCtx) { 148 149 boolean traceOn = isTraceOn(synCtx); 150 boolean traceOrDebugOn = isTraceOrDebugOn(traceOn); 151 152 if (traceOrDebugOn) { 153 traceOrDebug(traceOn, "Start : Script mediator"); 154 155 if (traceOn && trace.isTraceEnabled()) { 156 trace.trace("Message : " + synCtx.getEnvelope()); 157 } 158 } 159 160 if (traceOrDebugOn) { 161 traceOrDebug(traceOn, "Scripting language : " + language + " source " + 162 (key == null ? ": specified inline " : " loaded with key : " + key) + 163 (function != null ? " function : " + function : "")); 164 } 165 166 boolean returnValue; 167 if (multiThreadedEngine) { 168 returnValue = invokeScript(synCtx); 169 } else { 170 // TODO: change to use a pool of script engines (requires an update to BSF) 171 synchronized (scriptEngine.getClass()) { 172 returnValue = invokeScript(synCtx); 173 } 174 } 175 176 if (traceOn && trace.isTraceEnabled()) { 177 trace.trace("Result message after execution of script : " + synCtx.getEnvelope()); 178 } 179 180 if (traceOrDebugOn) { 181 traceOrDebug(traceOn, "End : Script mediator return value : " + returnValue); 182 } 183 184 return returnValue; 185 } 186 187 private boolean invokeScript(MessageContext synCtx) { 188 boolean returnValue; 189 try { 190 191 Object returnObject; 192 if (key != null) { 193 returnObject = mediateWithExternalScript(synCtx); 194 } else { 195 returnObject = mediateForInlineScript(synCtx); 196 } 197 if (returnObject != null && returnObject instanceof Boolean) { 198 returnValue = ((Boolean) returnObject).booleanValue(); 199 } else { 200 returnValue = true; 201 } 202 203 } catch (ScriptException e) { 204 handleException("The script engine returned an error executing the " + 205 (key == null ? "inlined " : "external ") + language + " script" + 206 (key != null? " : " + key : "") + 207 (function != null ? " function " + function : ""), e, synCtx); 208 returnValue = false; 209 } catch (NoSuchMethodException e) { 210 handleException("The script engine returned a NoSuchMethodException executing the " + 211 (key == null ? "inlined " : "external ") + language + " script" + 212 (key != null? " : " + key : "") + 213 (function != null ? " function " + function : ""), e, synCtx); 214 returnValue = false; 215 } 216 return returnValue; 217 } 218 219 /** 220 * Mediation implementation when the script to be executed should be loaded from the registry 221 * 222 * @param synCtx the message context 223 * @return script result 224 * @throws ScriptException 225 */ 226 protected Object mediateWithExternalScript(MessageContext synCtx) throws ScriptException, NoSuchMethodException { 227 prepareExternalScript(synCtx); 228 ScriptMessageContext scriptMC = new ScriptMessageContext(synCtx, xmlHelper); 229 return invocableScript.invokeFunction(function, new Object[]{scriptMC}); 230 } 231 232 /** 233 * Perform mediation with static inline script of the given scripting language 234 * 235 * @param synCtx message context 236 * @return true, or the script return value 237 * @throws ScriptException 238 */ 239 protected Object mediateForInlineScript(MessageContext synCtx) throws ScriptException { 240 241 ScriptMessageContext scriptMC = new ScriptMessageContext(synCtx, xmlHelper); 242 243 Bindings bindings = scriptEngine.createBindings(); 244 bindings.put(MC_VAR_NAME, scriptMC); 245 246 Object response; 247 if (compiledScript != null) { 248 response = compiledScript.eval(bindings); 249 } else { 250 response = scriptEngine.eval(scriptSourceCode, bindings); 251 } 252 253 return response; 254 255 } 256 257 /** 258 * Initialise the Mediator for the inline script 259 */ 260 protected void initInlineScript() { 261 try { 262 initScriptEngine(); 263 264 if (scriptEngine instanceof Compilable) { 265 if (log.isDebugEnabled()) { 266 log.debug("Script engine supports Compilable interface, compiling script code.."); 267 } 268 compiledScript = ((Compilable)scriptEngine).compile(scriptSourceCode); 269 } else { 270 // do nothing. If the script enging doesn't support Compilable then 271 // the inline script will be evaluated on each invocation 272 if (log.isDebugEnabled()) { 273 log.debug("Script engine does not support the Compilable interface, " + 274 "inlined script would be evaluated on each invocation.."); 275 } 276 } 277 278 } catch (ScriptException e) { 279 throw new SynapseException("Exception initializing inline script", e); 280 } 281 } 282 283 /** 284 * Prepares the mediator for the invocation of an external script 285 * 286 * @throws ScriptException 287 */ 288 protected synchronized void prepareExternalScript(MessageContext synCtx) throws ScriptException { 289 290 // TODO: only need this synchronized method for dynamic registry entries. If there was a way 291 // to access the registry entry during mediator initialization then for non-dynamic entries 292 // this could be done just the once during mediator initialization. 293 294 Entry entry = synCtx.getConfiguration().getEntryDefinition(key); 295 boolean needsReload = (entry != null) && entry.isDynamic() && (!entry.isCached() || entry.isExpired()); 296 synchronized (resourceLock) { 297 if (scriptSourceCode == null || needsReload) { 298 Object o = synCtx.getEntry(key); 299 if (o instanceof OMElement) { 300 scriptSourceCode = ((OMElement) (o)).getText(); 301 scriptEngine.eval(scriptSourceCode); 302 } else if (o instanceof String) { 303 scriptSourceCode = (String) o; 304 scriptEngine.eval(scriptSourceCode); 305 } else if (o instanceof OMText) { 306 307 DataHandler dataHandler = (DataHandler) ((OMText) o).getDataHandler(); 308 if (dataHandler != null) { 309 BufferedReader reader = null; 310 try { 311 reader = new BufferedReader( 312 new InputStreamReader(dataHandler.getInputStream())); 313 scriptEngine.eval(reader); 314 315 } catch (IOException e) { 316 handleException("Error in reading script as a stream ", e, synCtx); 317 } finally { 318 319 if (reader != null) { 320 try { 321 reader.close(); 322 } catch (IOException e) { 323 handleException("Error in closing input stream ", e, synCtx); 324 } 325 } 326 327 } 328 } 329 } 330 331 } 332 } 333 334 // load <include /> scripts; reload each script if needed 335 for (Iterator iter = includes.keySet().iterator(); iter.hasNext();) { 336 String includeKey = (String) iter.next(); 337 String includeSourceCode = (String) includes.get(includeKey); 338 Entry includeEntry = synCtx.getConfiguration().getEntryDefinition(includeKey); 339 boolean includeEntryNeedsReload = (includeEntry != null) && includeEntry.isDynamic() 340 && (!includeEntry.isCached() || includeEntry.isExpired()); 341 synchronized (resourceLock) { 342 if (includeSourceCode == null || includeEntryNeedsReload) { 343 log.debug("Re-/Loading the include script with key " + includeKey); 344 Object o = synCtx.getEntry(includeKey); 345 if (o instanceof OMElement) { 346 includeSourceCode = ((OMElement) (o)).getText(); 347 scriptEngine.eval(includeSourceCode); 348 } else if (o instanceof String) { 349 includeSourceCode = (String) o; 350 scriptEngine.eval(includeSourceCode); 351 } else if (o instanceof OMText) { 352 353 DataHandler dataHandler = (DataHandler) ((OMText) o).getDataHandler(); 354 if (dataHandler != null) { 355 BufferedReader reader = null; 356 try { 357 reader = new BufferedReader( 358 new InputStreamReader(dataHandler.getInputStream())); 359 scriptEngine.eval(reader); 360 361 } catch (IOException e) { 362 handleException("Error in reading script as a stream ", e, synCtx); 363 } finally { 364 365 if (reader != null) { 366 try { 367 reader.close(); 368 } catch (IOException e) { 369 handleException("Error in closing input" + 370 " stream ", e, synCtx); 371 } 372 } 373 } 374 } 375 } 376 } 377 } 378 } 379 } 380 381 protected void initScriptEngine() { 382 if (log.isDebugEnabled()) { 383 log.debug("Initializing script mediator for language : " + language); 384 } 385 386 ScriptEngineManager manager = new ScriptEngineManager(); 387 this.scriptEngine = manager.getEngineByExtension(language); 388 if (scriptEngine == null) { 389 handleException("No script engine found for language: " + language); 390 } 391 xmlHelper = XMLHelper.getArgHelper(scriptEngine); 392 393 this.multiThreadedEngine = scriptEngine.getFactory().getParameter("THREADING") != null; 394 log.debug("Script mediator for language : " + language + 395 " supports multithreading? : " + multiThreadedEngine); 396 } 397 398 public String getLanguage() { 399 return language; 400 } 401 402 public String getKey() { 403 return key; 404 } 405 406 public String getFunction() { 407 return function; 408 } 409 410 public String getScriptSrc() { 411 return scriptSourceCode; 412 } 413 414 private void handleException(String msg) { 415 log.error(msg); 416 throw new SynapseException(msg); 417 } 418 419 public Map getIncludeMap() { 420 return includes; 421 } 422 423 public void setIncludeMap(Map includeMap) { 424 this.includes = includeMap; 425 } 426 427 }