Java tutorial
/* * Copyright (c) Mirth Corporation. All rights reserved. * http://www.mirthcorp.com * * The software in this package is published under the terms of the MPL * license a copy of which has been included with this distribution in * the LICENSE.txt file. */ package com.mirth.connect.server.util; import java.util.Properties; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.log4j.Logger; import org.mozilla.javascript.Context; import org.mozilla.javascript.EvaluatorException; import org.mozilla.javascript.ImporterTopLevel; import org.mozilla.javascript.RhinoException; import org.mozilla.javascript.Script; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mule.umo.UMOEventContext; import com.mirth.connect.model.CodeTemplate; import com.mirth.connect.model.CodeTemplate.CodeSnippetType; import com.mirth.connect.model.MessageObject; import com.mirth.connect.model.Event; import com.mirth.connect.server.MirthJavascriptTransformerException; import com.mirth.connect.server.controllers.ControllerException; import com.mirth.connect.server.controllers.ControllerFactory; import com.mirth.connect.server.controllers.EventController; import com.mirth.connect.server.controllers.ScriptController; import com.mirth.connect.util.PropertyLoader; public class JavaScriptUtil { private Logger logger = Logger.getLogger(this.getClass()); private CompiledScriptCache compiledScriptCache = CompiledScriptCache.getInstance(); private static ScriptableObject sealedSharedScope; private static final int SOURCE_CODE_LINE_WRAPPER = 5; private int rhinoOptimizationLevel = -1; // singleton pattern private static JavaScriptUtil instance = null; private JavaScriptUtil() { } public static JavaScriptUtil getInstance() { synchronized (JavaScriptUtil.class) { if (instance == null) { instance = new JavaScriptUtil(); instance.initialize(); } return instance; } } private void initialize() { /* * Checks mirth.properties for the rhino.optimizationlevel property. * Setting it to -1 runs it in interpretive mode. See MIRTH-1627 for * more information. */ Properties properties = PropertyLoader.loadProperties("mirth"); if (MapUtils.isNotEmpty(properties) && properties.containsKey("rhino.optimizationlevel")) { rhinoOptimizationLevel = Integer.valueOf(properties.getProperty("rhino.optimizationlevel")).intValue(); logger.debug("set Rhino context optimization level: " + rhinoOptimizationLevel); } else { logger.debug("using defualt Rhino context optimization level (-1)"); } } public Context getContext() { Context context = Context.enter(); context.setOptimizationLevel(rhinoOptimizationLevel); if (sealedSharedScope == null) { String importScript = getJavascriptImportScript(); sealedSharedScope = new ImporterTopLevel(context); JavaScriptScopeUtil.buildScope(sealedSharedScope); Script script = context.compileString(importScript, UUIDGenerator.getUUID(), 1, null); script.exec(context, sealedSharedScope); sealedSharedScope.sealObject(); } return context; } public String getJavascriptImportScript() { StringBuilder script = new StringBuilder(); // add #trim() function to JS String script.append( "String.prototype.trim = function() { return this.replace(/^\\s+|\\s+$/g,\"\").replace(/^\\t+|\\t+$/g,\"\"); };"); script.append("importPackage(Packages.com.mirth.connect.server.util);\n"); script.append("importPackage(Packages.com.mirth.connect.model.converters);\n"); script.append("regex = new RegExp('');\n"); script.append("xml = new XML('');\n"); script.append("xmllist = new XMLList();\n"); script.append("namespace = new Namespace();\n"); script.append("qname = new QName();\n"); /* * Ignore whitespace so blank lines are removed when deleting elements. * This also involves changing XmlProcessor.java in Rhino to account for * Rhino issue 369394 and MIRTH-1405 */ script.append("XML.ignoreWhitespace=true;"); // Setting prettyPrinting to true causes HL7 to break when converting // back from HL7. script.append("XML.prettyPrinting=false;"); return script.toString(); } public Scriptable getScope() { Scriptable scope = getContext().newObject(sealedSharedScope); scope.setPrototype(sealedSharedScope); scope.setParentScope(null); return scope; } /** * Executes the global and channel preprocessor scripts in order, building * up the necessary scope for the global preprocessor and adding the result * back to it for the channel preprocessor. * */ public String executePreprocessorScripts(String message, UMOEventContext muleContext, String channelId) { String result = message; Scriptable scope = getScope(); Logger scriptLogger = Logger.getLogger(ScriptController.PREPROCESSOR_SCRIPT_KEY.toLowerCase()); JavaScriptScopeUtil.buildScopeForPreprocessor(scope, message, channelId, muleContext, scriptLogger); try { // Execute the global preprocessor and check the result Object globalResult = executeScript(ScriptController.PREPROCESSOR_SCRIPT_KEY, scope); if (globalResult != null) { String processedMessage = (String) Context.jsToJava(globalResult, java.lang.String.class); if (processedMessage != null) { result = processedMessage; // Put the new message in the scope before the channel // preprocessor is executed scope.put("message", scope, result); } } } catch (Exception e) { logScriptError(ScriptController.PREPROCESSOR_SCRIPT_KEY, null, e); } try { // Execute the channel preprocessor using the result of the global // preprocessor (if any) Object channelResult = executeScript(channelId + "_" + ScriptController.PREPROCESSOR_SCRIPT_KEY, scope); if (channelResult != null) { String processedMessage = (String) Context.jsToJava(channelResult, java.lang.String.class); if (processedMessage != null) { result = processedMessage; } } } catch (Exception e) { logScriptError(ScriptController.PREPROCESSOR_SCRIPT_KEY, channelId, e); } return result; } /** * Executes the channel postprocessor, followed by the global postprocessor. * * @param messageObject */ public void executePostprocessorScripts(MessageObject messageObject) { Scriptable scope = getScope(); Logger scriptLogger = Logger.getLogger(ScriptController.POSTPROCESSOR_SCRIPT_KEY.toLowerCase()); JavaScriptScopeUtil.buildScope(scope, messageObject, scriptLogger); try { executeScript(messageObject.getChannelId() + "_" + ScriptController.POSTPROCESSOR_SCRIPT_KEY, scope); } catch (Exception e) { logScriptError(ScriptController.POSTPROCESSOR_SCRIPT_KEY, messageObject.getChannelId(), e); } try { executeScript(ScriptController.POSTPROCESSOR_SCRIPT_KEY, scope); } catch (Exception e) { logScriptError(ScriptController.POSTPROCESSOR_SCRIPT_KEY, null, e); } } /** * Executes channel level deploy or shutdown scripts. * * @param scriptId * @param scriptType * @param channelId */ public void executeChannelDeployOrShutdownScript(String scriptId, String scriptType, String channelId) { try { Scriptable scope = getScope(); Logger scriptLogger = Logger.getLogger(scriptType.toLowerCase()); JavaScriptScopeUtil.buildScope(scope, channelId, scriptLogger); executeScript(scriptId, scope); } catch (Exception e) { logScriptError(scriptId, channelId, e); } } /** * Executes global level deploy or shutdown scripts. * * @param scriptId */ public void executeGlobalDeployOrShutdownScript(String scriptId) { try { Scriptable scope = getScope(); Logger scriptLogger = Logger.getLogger(scriptId.toLowerCase()); JavaScriptScopeUtil.buildScope(scope, scriptLogger); executeScript(scriptId, scope); } catch (Exception e) { logScriptError(scriptId, null, e); } } /** * Logs out a script error with the script type and the script level * (channelId or global). * * @param scriptType * @param channelId * @param e */ private void logScriptError(String scriptType, String channelId, Exception e) { EventController eventController = ControllerFactory.getFactory().createEventController(); String error = "Error executing " + scriptType + " script from channel: "; if (StringUtils.isNotEmpty(channelId)) { error += channelId; } else { error += "Global"; } Event event = new Event(error); event.setLevel(Event.Level.ERROR); event.getAttributes().put(Event.ATTR_EXCEPTION, ExceptionUtils.getStackTrace(e)); eventController.addEvent(event); logger.error(error, e); } /** * Executes the script with the given scriptId and scope. * * @param scriptId * @param scope * @return * @throws Exception */ private Object executeScript(String scriptId, Scriptable scope) throws Exception { Script compiledScript = compiledScriptCache.getCompiledScript(scriptId); if (compiledScript == null) { return null; } try { logger.debug("executing script: id=" + scriptId); return compiledScript.exec(Context.enter(), scope); } catch (Exception e) { if (e instanceof RhinoException) { String script = compiledScriptCache.getSourceScript(scriptId); String sourceCode = JavaScriptUtil.getInstance().getSourceCode(script, ((RhinoException) e).lineNumber(), 0); e = new MirthJavascriptTransformerException((RhinoException) e, null, null, 0, null, sourceCode); } throw e; } finally { Context.exit(); } } public boolean compileAndAddScript(String scriptId, String script, String defaultScript, boolean includeChannelMap, boolean includeGlobalChannelMap, boolean includeMuleContext) throws Exception { // Note: If the defaultScript is NULL, this means that the script should // always be inserted without being compared. Context context = getContext(); boolean scriptInserted = false; String generatedScript = null; try { logger.debug("compiling script " + scriptId); generatedScript = generateScript(script, includeChannelMap, includeGlobalChannelMap, includeMuleContext); Script compiledScript = context.compileString(generatedScript, scriptId, 1, null); String decompiledScript = context.decompileScript(compiledScript, 0); String decompiledDefaultScript = null; if (defaultScript != null) { Script compiledDefaultScript = context.compileString(generateScript(defaultScript, includeChannelMap, includeGlobalChannelMap, includeMuleContext), scriptId, 1, null); decompiledDefaultScript = context.decompileScript(compiledDefaultScript, 0); } if ((defaultScript == null) || !decompiledScript.equals(decompiledDefaultScript)) { logger.debug("adding script " + scriptId); compiledScriptCache.putCompiledScript(scriptId, compiledScript, generatedScript); scriptInserted = true; } } catch (EvaluatorException e) { if (e instanceof RhinoException) { String sourceCode = JavaScriptUtil.getInstance().getSourceCode(generatedScript, ((RhinoException) e).lineNumber(), 0); MirthJavascriptTransformerException mjte = new MirthJavascriptTransformerException( (RhinoException) e, null, null, 0, scriptId, sourceCode); throw new Exception(mjte); } else { throw new Exception(e); } } finally { Context.exit(); } return scriptInserted; } public String generateScript(String script, boolean includeChannelMap, boolean includeGlobalChannelMap, boolean includeMuleContext) { StringBuilder builtScript = new StringBuilder(); builtScript.append( "String.prototype.trim = function() { return this.replace(/^\\s+|\\s+$/g,\"\").replace(/^\\t+|\\t+$/g,\"\"); };"); builtScript.append("function $(string) { "); if (includeChannelMap) { builtScript.append("if (channelMap.containsKey(string)) { return channelMap.get(string);} else "); } if (includeGlobalChannelMap) { builtScript.append( "if (globalChannelMap.containsKey(string)) { return globalChannelMap.get(string);} else "); } builtScript.append("if (globalMap.containsKey(string)) { return globalMap.get(string);} else "); builtScript.append("{ return ''; }}"); if (includeChannelMap) { builtScript.append("function $c(key, value){"); builtScript.append("if (arguments.length == 1){return channelMap.get(key); }"); builtScript.append("else if (arguments.length == 2){channelMap.put(key, value); }}"); builtScript.append("function $co(key, value){"); builtScript.append("if (arguments.length == 1){return connectorMap.get(key); }"); builtScript.append("else if (arguments.length == 2){connectorMap.put(key, value); }}"); builtScript.append("function $r(key, value){"); builtScript.append("if (arguments.length == 1){return responseMap.get(key); }"); builtScript.append("else if (arguments.length == 2){responseMap.put(key, value); }}"); // Helper function to access attachments (returns List<Attachment>) builtScript.append("function getAttachments() {"); builtScript.append( "return Packages.com.mirth.connect.server.controllers.MessageObjectController.getInstance().getAttachmentsByMessage(messageObject);"); builtScript.append("}"); // Helper function to set attachment builtScript.append("function addAttachment(data, type) {"); builtScript.append( "var attachment = Packages.com.mirth.connect.server.controllers.MessageObjectController.getInstance().createAttachment(data, type, messageObject);"); builtScript.append( "Packages.com.mirth.connect.server.controllers.MessageObjectController.getInstance().insertAttachment(attachment); \n"); builtScript.append("return attachment; }\n"); } if (includeMuleContext) { /* * If the message object is not available (i.e. in the * preprocessor), then add a different version of the addAttachment * function that adds the attachment to the mule context for * insertion in JavaScriptTransformer#transform. */ builtScript.append("function addAttachment(data, type) {"); builtScript.append( "var attachment = Packages.com.mirth.connect.server.controllers.MessageObjectController.getInstance().createAttachment(data, type);"); builtScript.append("muleContext.getProperties().get('attachments').add(attachment); \n"); builtScript.append("return attachment; }\n"); } if (includeGlobalChannelMap) { builtScript.append("function $gc(key, value){"); builtScript.append("if (arguments.length == 1){return globalChannelMap.get(key); }"); builtScript.append("else if (arguments.length == 2){globalChannelMap.put(key, value); }}"); } builtScript.append("function $g(key, value){"); builtScript.append("if (arguments.length == 1){return globalMap.get(key); }"); builtScript.append("else if (arguments.length == 2){globalMap.put(key, value); }}"); try { for (CodeTemplate template : ControllerFactory.getFactory().createCodeTemplateController() .getCodeTemplate(null)) { if (template.getType() == CodeSnippetType.FUNCTION) { if (template.getScope() == CodeTemplate.ContextType.GLOBAL_CONTEXT.getContext() || template .getScope() == CodeTemplate.ContextType.GLOBAL_CHANNEL_CONTEXT.getContext()) { builtScript.append(template.getCode()); } else if (includeChannelMap && template.getScope() == CodeTemplate.ContextType.CHANNEL_CONTEXT.getContext()) { builtScript.append(template.getCode()); } } } } catch (ControllerException e) { logger.error("Could not get user functions.", e); } builtScript.append("function doScript() {\n" + script + " \n}\n"); builtScript.append("doScript()\n"); return builtScript.toString(); } public void removeScriptFromCache(String scriptId) { if (compiledScriptCache.getCompiledScript(scriptId) != null) { compiledScriptCache.removeCompiledScript(scriptId); } } /** * Utility to get source code from script. Used to generate error report. * * @param script * @param errorLineNumber * @param offset * @return */ public String getSourceCode(String script, int errorLineNumber, int offset) { String[] lines = script.split("\n"); int startingLineNumber = errorLineNumber - offset; /* * If the starting line number is 5 or less, set it to 6 so that it * displays lines 1-11 (0-10 in the array) */ if (startingLineNumber <= SOURCE_CODE_LINE_WRAPPER) { startingLineNumber = SOURCE_CODE_LINE_WRAPPER + 1; } int currentLineNumber = startingLineNumber - SOURCE_CODE_LINE_WRAPPER; StringBuilder source = new StringBuilder(); while ((currentLineNumber < (startingLineNumber + SOURCE_CODE_LINE_WRAPPER)) && (currentLineNumber < lines.length)) { source.append( System.getProperty("line.separator") + currentLineNumber + ": " + lines[currentLineNumber - 1]); currentLineNumber++; } return source.toString(); } }