com.CodeSeance.JSeance.CodeGenXML.ContextManager.java Source code

Java tutorial

Introduction

Here is the source code for com.CodeSeance.JSeance.CodeGenXML.ContextManager.java

Source

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is JSeance code, released
* May 6, 2009.
*
* The Initial Developer of the Original Code is
* Andres Murillo.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */

package com.CodeSeance.JSeance.CodeGenXML;

import com.CodeSeance.JSeance.CodeGenXML.DependencyTracking.TemplateDependencies;
import org.apache.commons.logging.Log;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.xml.XMLObject;
import org.w3c.dom.Document;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.StringWriter;
import java.util.Stack;

/**
 * This is class serves a dual purpose, it encapsulates the functionality of the JavaScript engine (Rhino) and manages
 * the stack of Context objets that become active as Template scopes execute.
 *
 * @author Andres Murillo
 * @version 1.0
 */
public class ContextManager {
    /*
    * Constructor initialized the JavaScript engine and pushes a new context on the stack,
    * note that the thread that created the context manager should be the same calling any
    * action on it or its children (rhino js engine requirement).
    * dispose needs to be called when the ContextManager is no longer needed to release
    * resources
    */
    public ContextManager(File includesDir, File modelsDir, File targetDir, boolean ignoreReadOnlyOuputFiles,
            TemplateDependencies templateDependencies) {
        this.includesDir = includesDir;
        this.modelsDir = modelsDir;
        this.targetDir = targetDir;
        this.ignoreReadOnlyOuputFiles = ignoreReadOnlyOuputFiles;
        this.templateDependencies = templateDependencies;
        initializeJavaScriptEngine();
        contextStack.push(new Context(this, null));
    }

    // The working directories and runtime configuration
    public final File includesDir;
    public final File modelsDir;
    public final File targetDir;
    public final boolean ignoreReadOnlyOuputFiles;

    // Dependency manager, readers and writers need to use it
    public final TemplateDependencies templateDependencies;

    // The context factory to be used across all instances
    final static ContextFactory factory = new ContextFactory();

    private final static String XML_CREATE_FN = "JSeance_CreateXML";
    private final static String XML_EVAL_PATH_FN = "JSeance_EvalXMLPath";
    private final static String XML_LENGTH_FN = "JSeance_XMLLength";
    private final static String XML_GET_NODE_AT_FN = "JSeance_XMLGetNodeAt";
    private final static String XML_NODE_TO_STRING = "JSeance_XMLNodeToString";

    //Initializes the JavaScript engine (Rhino) with the required context objects and instances
    private void initializeJavaScriptEngine() {
        jsContext = factory.enterContext();
        try {
            if (ExecutionError.simulate_CONTEXTMANAGER_INITIALIZE_ERROR) {
                ExecutionError.simulate_CONTEXTMANAGER_INITIALIZE_ERROR = false;
                throw new Exception("Simulated exception for log testing");
            }

            jsScope = jsContext.initStandardObjects();

            // Declare the java classes that implement models and definitions in the js engine
            ScriptableObject.defineClass(jsScope, JSModels.class);
            ScriptableObject.defineClass(jsScope, JSModel.class);
            ScriptableObject.defineClass(jsScope, JSDefinitions.class);
        } catch (Exception ex) {
            // Wrap Exception with RuntimeException since caller won't be able to handle it
            throw new RuntimeException(ExecutionError.CONTEXTMANAGER_INITIALIZE_ERROR.getMessage(ex.getMessage()));
        }

        evaluateJSPrivate("function " + XML_CREATE_FN + "(xmlText){return new XML(xmlText);};",
                "ContextManager.java", 123);
        evaluateJSPrivate("function " + XML_EVAL_PATH_FN + "(xml, path){return eval('xml.' + path);};",
                "ContextManager.java", 124);
        evaluateJSPrivate("function " + XML_LENGTH_FN + "(xml){return xml.length();};", "ContextManager.java", 125);
        evaluateJSPrivate("function " + XML_GET_NODE_AT_FN + "(xml, index){return xml[index];};",
                "ContextManager.java", 126);
        evaluateJSPrivate("function " + XML_NODE_TO_STRING + "(xml){return xml.toXMLString();};",
                "ContextManager.java", 127);

        declareStringConversionFunction("EscapeXMLValue",
                "com.CodeSeance.JSeance.CodeGenXML.XMLElements.Text.escapeXMLValue");
        declareStringConversionFunction("EscapeXMLAttribute",
                "com.CodeSeance.JSeance.CodeGenXML.XMLElements.Text.escapeXMLAttribute");
        declareStringConversionFunction("EscapeHTML", "org.apache.commons.lang.StringEscapeUtils.escapeHtml");
        declareStringConversionFunction("EscapeJava", "org.apache.commons.lang.StringEscapeUtils.escapeJava");
        declareStringConversionFunction("EscapeJavaScript",
                "org.apache.commons.lang.StringEscapeUtils.escapeJavaScript");
        declareStringConversionFunction("EscapeSQL", "org.apache.commons.lang.StringEscapeUtils.escapeSql");
    }

    private void declareStringConversionFunction(String name, String method) {
        evaluateJSPrivate(String.format("function %s (val){return String(%s(val));};", name, method),
                "ContextManager.java", 139);
    }

    public void setCurrentDefinitions(JSDefinitions jsDefinitions) {
        ScriptableObject.putProperty(jsScope, "Definitions", jsDefinitions);
    }

    public void setCurrentModels(JSModels jsModels) {
        ScriptableObject.putProperty(jsScope, "Models", jsModels);
    }

    // Class logger
    private Log log = Runtime.CreateLogger(this.getClass());

    /*
    * Transforms a Java XML Document into a JavaScript XMLObject that can be used within a script context
     */
    public XMLObject createXMLObject(Document document) {
        //Transform to String
        Transformer transformer;
        StreamResult result;
        try {
            if (ExecutionError.simulate_CONTEXTMANAGER_CREATEXMLOBJECT_ERROR) {
                ExecutionError.simulate_CONTEXTMANAGER_CREATEXMLOBJECT_ERROR = false;
                throw new TransformerException("Simulated exception for log testing");
            }
            transformer = TransformerFactory.newInstance().newTransformer();
            // The next section is required in order for the JS engine to parse the XML
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            result = new StreamResult(new StringWriter());
            DOMSource source = new DOMSource(document);
            transformer.transform(source, result);
        } catch (TransformerException ex) {
            // Wrap Exception with RuntimeException since caller won't be able to handle it
            throw new RuntimeException(
                    ExecutionError.CONTEXTMANAGER_CREATEXMLOBJECT_ERROR.getMessage(ex.getMessage()));
        }

        String xmlString = result.getWriter().toString();

        // Create the JS Objects
        Object[] args = { xmlString };
        Object jsXMLObj = callJSFunction(XML_CREATE_FN, args);
        assert jsXMLObj instanceof XMLObject;
        return (XMLObject) jsXMLObj;
    }

    /*
    * Evaluates the specified E4X Path on a JavaScript XML Node
     */
    public Object evaluateE4XPath(XMLObject xmlObj, String e4XPath) {
        Object result = xmlObj;
        if (!"".equals(e4XPath) && e4XPath != null) {
            Object[] args = { xmlObj, e4XPath };
            result = callJSFunction(XML_EVAL_PATH_FN, args);
        }
        return result;
    }

    /*
    * Evaluates the specified E4X Path on a JavaScript XML Node
    */
    public String xmlNodeToString(XMLObject xmlObj) {
        Object[] args = { xmlObj };
        return callJSFunction(XML_NODE_TO_STRING, args).toString();
    }

    /*
    * returns the collection size of a Javascript NodeList
    */
    public int getXMLCollectionSize(XMLObject xmlObj) {
        Object[] args = { xmlObj };
        Object result = callJSFunction(XML_LENGTH_FN, args);
        assert result instanceof Integer;
        return (Integer) result;
    }

    /*
    * Returns a JavaScript XML Node at the specified place in the NodeList
    */
    public XMLObject getXMLItemAt(XMLObject xmlObj, int index) {
        Object[] args = { xmlObj, index };
        Object result = callJSFunction(XML_GET_NODE_AT_FN, args);
        assert result instanceof XMLObject;
        return (XMLObject) result;
    }

    // JSEngine context variables
    org.mozilla.javascript.Context jsContext = null;
    org.mozilla.javascript.Scriptable jsScope = null;

    // Pushes a new Context into the stack
    public void push() {
        Context newContext = new Context(this, contextStack.peek());
        contextStack.push(newContext);
    }

    // Pops a context from the stack
    public Context pop() {
        return contextStack.pop();
    }

    // Returns the current context from the top of the stack without changing it
    public Context getCurrentContext() {
        return contextStack.peek();
    }

    // Releases the resources assciated with the JSEngine attached to the current thread
    public void dispose() {
        org.mozilla.javascript.Context.exit();
    }

    // Evaluates the specified JavaScript text - Public version useful for logging & breakpoints
    public Object evaluateJS(String script, String sourceName, int lineNo) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Evaluating JavaScript:[%s]", script));
        }
        return evaluateJSPrivate(script, sourceName, lineNo);
    }

    // Evaluates the specified JavaScript text - Private version, not logged
    private Object evaluateJSPrivate(String script, String sourceName, int lineNo) {
        try {
            return jsContext.evaluateString(jsScope, script, sourceName, lineNo, null);
        } catch (Exception ex) {
            throw new RuntimeException(ExecutionError.JAVASCRIPT_EVAL_ERROR.getMessage(script, ex.getMessage()));
        }
    }

    // Calls the specified Javascript function
    public Object callJSFunction(String functionName, Object[] fxArgs) {
        Object fxObj = jsScope.get(functionName, jsScope);
        assert fxObj instanceof org.mozilla.javascript.Function;
        org.mozilla.javascript.Function fx = (org.mozilla.javascript.Function) fxObj;
        return fx.call(jsContext, jsScope, jsScope, fxArgs);
    }

    // Prefix to be used when embedding JavaScript code into text
    public final static String[] CODE_PREFIXES = { "@JavaScript{", "@JS{" };
    // Suffix to be used when embedding JavaScript code into text
    public final static String CODE_SUFFIX = "}@";

    // Resolves the embedded script fragments within a piece of texts
    public String resolveCodeFragments(String input) {
        String result = input;
        for (String codePrefix : CODE_PREFIXES) {
            int locStart = input.indexOf(codePrefix);
            if (locStart >= 0) {
                StringBuilder builder = new StringBuilder();
                int locEnd = input.indexOf(CODE_SUFFIX, locStart);
                if (locEnd < 0) {
                    throw new RuntimeException(ExecutionError.JAVASCRIPT_NOT_CLOSED.getMessage(input, CODE_SUFFIX));
                }
                String subExpression = input.substring(locStart + codePrefix.length(), locEnd);
                builder.append(input.substring(0, locStart));
                builder.append(evaluateJS(subExpression, input, 0));
                builder.append(input.substring(locEnd + CODE_SUFFIX.length()));
                result = resolveCodeFragments(builder.toString());
            }
        }
        return result;
    }

    // The stack used to maintain state as the XML Template document gets explored
    private Stack<Context> contextStack = new Stack<Context>();
}