org.xchain.namespaces.jsl.AbstractTemplateCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.xchain.namespaces.jsl.AbstractTemplateCommand.java

Source

/**
 *    Copyright 2011 meltmedia
 *
 *    Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.xchain.namespaces.jsl;

import java.util.LinkedList;

import org.xchain.Command;
import org.xchain.Filter;
import org.xchain.Locatable;
import org.xchain.Registerable;
import org.xchain.framework.lifecycle.Execution;
import org.xchain.framework.sax.CommandXmlReader;
import org.xchain.framework.sax.CommandHandler;
import org.xchain.impl.ChainImpl;
import static org.xchain.namespaces.jsl.CommandExecutionState.*;
import org.xchain.namespaces.sax.PipelineCommand;
import org.xchain.framework.jxpath.ScopedJXPathContextImpl;

import org.apache.commons.jxpath.JXPathContext;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.Locator;

import javax.xml.namespace.QName;

/**
 * The base class for generated jsl template commands.
 * 
 * @author Christian Trimble
 * @author Jason Rose
 */
public abstract class AbstractTemplateCommand extends ChainImpl implements Locatable, Registerable {
    /** The thread local stack of command execution state arrays. */
    public static final ThreadLocal<LinkedList<CommandExecutionState[]>> commandExecutionStateStackTL = new ThreadLocal<LinkedList<CommandExecutionState[]>>();

    /** The thread local stack of element output state arrays. */
    public static final ThreadLocal<LinkedList<ElementOutputState[]>> elementOutputStateStackTL = new ThreadLocal<LinkedList<ElementOutputState[]>>();

    public static final ThreadLocal<LinkedList<QName>> dynamicElementStackTL = new ThreadLocal<LinkedList<QName>>();

    public static final ThreadLocal<SAXException> saxExceptionTl = new ThreadLocal<SAXException>();

    public static final ThreadLocal<Integer> depthTl = new ThreadLocal<Integer>();

    /**
     * Returns the command execute state array for the current thread.
     */
    protected static CommandExecutionState[] getCommandExecutionState() {
        LinkedList<CommandExecutionState[]> stack = commandExecutionStateStackTL.get();

        if (stack == null || stack.isEmpty()) {
            throw new IllegalStateException("getCommandExecutionState() called outside of execute method.");
        }

        return stack.getFirst();
    }

    protected static ElementOutputState[] getElementOutputState() {
        LinkedList<ElementOutputState[]> stack = elementOutputStateStackTL.get();

        if (stack == null || stack.isEmpty()) {
            throw new IllegalStateException("getElementOutputState() called outside of execute method.");
        }

        return stack.getFirst();
    }

    protected static LinkedList<QName> getDynamicElementStack() {
        LinkedList<QName> stack = dynamicElementStackTL.get();
        if (stack == null) {
            throw new IllegalStateException("getDynamicElementStack() called outside of execute method.");
        }
        return stack;
    }

    protected Locator locator;
    private int elementCount;
    protected String systemId = null;
    protected QName qName = null;
    protected int templateDepth = 0;

    public AbstractTemplateCommand(int elementCount) {
        this.elementCount = elementCount;
    }

    public boolean isRegistered() {
        return qName != null && systemId != null;
    }

    public void setQName(QName qName) {
        this.qName = qName;
    }

    public QName getQName() {
        return this.qName;
    }

    public void setSystemId(String systemId) {
        this.systemId = systemId;
    }

    public String getSystemId() {
        return this.systemId;
    }

    /**
     * Pushes a new command execution state array onto the stack.  The new state array has its values initialized to PRE_EXECUTE.
     */
    private final void pushCommandExecutionState() {
        // create the state array.
        int childCount = getCommandList().size();
        CommandExecutionState[] state = new CommandExecutionState[childCount];
        for (int i = 0; i < childCount; i++) {
            state[i] = PRE_EXECUTE;
        }

        // get the stack, creating it if it is missing.
        LinkedList<CommandExecutionState[]> stack = commandExecutionStateStackTL.get();
        if (stack == null) {
            stack = new LinkedList<CommandExecutionState[]>();
            commandExecutionStateStackTL.set(stack);
        }

        // push the state array.
        stack.addFirst(state);
    }

    /**
     * Pops the current command execution state array from the stack.
     */
    private final void popCommandExecutionState() {
        // get the stack for the current thread.
        LinkedList<CommandExecutionState[]> stack = commandExecutionStateStackTL.get();

        // if there was not a stack, then we are in an illegal state.
        if (stack == null) {
            throw new IllegalStateException(
                    "popCommandExecutionState() called when there was not a current stack.");
        }

        // remove the state array from the stack.
        stack.removeFirst();

        // if the stack is now empty, clean the thread local up.
        if (stack.isEmpty()) {
            commandExecutionStateStackTL.remove();
        }
    }

    private final void pushElementOutputState() {
        // create the state array.
        ElementOutputState[] state = new ElementOutputState[elementCount];
        for (int i = 0; i < elementCount; i++) {
            state[i] = ElementOutputState.PRE_START;
        }

        // get the stack, creating it if it is missing.
        LinkedList<ElementOutputState[]> stack = elementOutputStateStackTL.get();
        if (stack == null) {
            stack = new LinkedList<ElementOutputState[]>();
            elementOutputStateStackTL.set(stack);
        }

        // push the state array.
        stack.addFirst(state);
    }

    private final void popElementOutputState() {
        // get the stack for the current thread.
        LinkedList<ElementOutputState[]> stack = elementOutputStateStackTL.get();

        // if there was not a stack, then we are in an illegal state.
        if (stack == null) {
            throw new IllegalStateException("popElementOutputState() called when there was not a current stack.");
        }

        // remove the state array from the stack.
        stack.removeFirst();

        // if the stack is now empty, clean the thread local up.
        if (stack.isEmpty()) {
            elementOutputStateStackTL.remove();
        }

    }

    private final void pushHandlerInfo(JXPathContext context) {

    }

    private final void popHandlerInfo() {

    }

    /**
     * Uses the specified nameXPath and namespaceXPath to create a dynamic qName.
     */
    protected QName dynamicQName(JXPathContext context, String nameXPath, String namespaceXPath,
            boolean includeDefaultPrefix) throws SAXException {
        String name = (String) context.getValue(nameXPath, String.class);
        if (name == null) {
            throw new SAXException("QNames cannot have null names.");
        }
        String namespace = null;
        if (namespaceXPath != null) {
            namespace = (String) context.getValue(namespaceXPath, String.class);
            if (namespace == null) {
                throw new SAXException("Namespace uris cannot be null.");
            }
        }

        String[] parts = name.split(":", 2);
        String prefixPart = parts.length == 2 ? parts[0] : "";
        String localPart = parts.length == 2 ? parts[1] : parts[0];

        // if the prefix is not "", then it must be defined in the context.
        String prefixPartNamespace = (includeDefaultPrefix || !"".equals(prefixPart))
                ? context.getNamespaceURI(prefixPart)
                : "";
        if (!"".equals(prefixPart) && prefixPartNamespace == null) {
            throw new SAXException("The prefix '" + prefixPart + "' is not defined in the context.");
        }

        // ASSERT: if the prefix is not "", then it is defined in the context.

        // if the namespace is null, then the prefix defines the namespace.
        if (namespace == null) {
            return new QName(prefixPartNamespace != null ? prefixPartNamespace : "", localPart, prefixPart);
        }

        // ASSERT: the namespace was specified.

        // if the prefix is "", then we can lookup the proper namespace.
        if ("".equals(prefixPart)) {
            if (!namespace.equals(prefixPartNamespace)) {
                String namespacePrefix = ((ScopedJXPathContextImpl) context).getPrefix(namespace);
                if (namespacePrefix == null) {
                    throw new SAXException("The namespace '" + namespace + "' is not bound to a prefix.");
                }
                if (!includeDefaultPrefix && "".equals(namespacePrefix)) {
                    throw new SAXException("The namespace '" + namespace + "' cannot be used for the attribute '"
                            + name + "', because it maps to the default namespace.");
                }
                return new QName(namespace, localPart, namespacePrefix);
            } else {
                return new QName(namespace, localPart, prefixPart);
            }
        }

        // ASSERT: the prefix is defined and the namespace is defined, if they do not match, we must fail.
        if (!namespace.equals(prefixPartNamespace)) {
            throw new SAXException("The prefix '" + prefixPart + "' is bound to '" + prefixPartNamespace
                    + "', but the namespace '" + namespace + "' is required.");
        }

        // ASSERT: the namespace and prefix will work together.
        return new QName(namespace, localPart, prefixPart);
    }

    protected void trackStartElement(int elementIndex) {
        getElementOutputState()[elementIndex] = ElementOutputState.STARTED;
    }

    protected void trackEndElement(int elementIndex) {
        getElementOutputState()[elementIndex] = ElementOutputState.ENDED;
    }

    public boolean isElementStarted(int elementIndex) {
        return getElementOutputState()[elementIndex] == ElementOutputState.STARTED;
    }

    public void setLocator(Locator locator) {
        this.locator = locator;
    }

    public Locator getLocator() {
        return locator;
    }

    /**
     * Initializes the internal state of this command and then calls executeTemplate( JXPathContext ).
     */
    public final boolean execute(JXPathContext context) throws Exception {
        boolean inExecution = Execution.inExecution();
        boolean createDynamicElementStack = dynamicElementStackTL.get() == null;
        boolean result = false;

        if (!inExecution) {
            Execution.startExecution(context);
        }
        context = Execution.startCommandExecute(this, context);
        try {
            if (depthTl.get() == null) {
                depthTl.set(new Integer(0));
            } else {
                depthTl.set(depthTl.get() + 1);
            }
            if (createDynamicElementStack) {
                dynamicElementStackTL.set(new LinkedList<QName>());
            }
            // push a new command execution state array onto the stack.
            pushCommandExecutionState();
            pushElementOutputState();

            // push the information about the current output document.
            pushHandlerInfo(context);

            result = executeTemplate(context);

            // if there was a sax exception registered and this is the outer most template, then we need to pass it up.
            if (depthTl.get().equals(new Integer(0)) && hasSaxExceptionFired()) {
                SAXException saxException = saxExceptionTl.get();
                saxExceptionTl.remove();
                throw saxException;
            }
        } catch (Exception e) {
            Execution.exceptionThrown(this, e);
            throw e;
        } finally {
            // pop the information about the current output document.
            popHandlerInfo();

            popElementOutputState();
            // pop the command execution state array off of the stack.
            popCommandExecutionState();

            if (createDynamicElementStack) {
                dynamicElementStackTL.remove();
            }

            if (depthTl.get().equals(new Integer(0))) {
                depthTl.remove();
            } else {
                depthTl.set(depthTl.get() - 1);
            }

            context = Execution.endCommandExecute(this, context);

            if (!inExecution) {
                Execution.endExecution();
            }
        }

        return result;
    }

    /**
     * Generated templates implement this method to provide sax output.
     */
    public abstract boolean executeTemplate(JXPathContext context) throws Exception;

    /**
     * Calls the execute method of the children specified by childIndecies, passing it the supplied context.
     * If the execute call completes without failing, then the command execution state for that
     * child command is updated to EXECUTED.
     */
    protected final boolean executeChildren(JXPathContext context, int[] childIndecies) throws Exception {
        boolean result = false;
        for (int i = 0; !result && i < childIndecies.length; i++) {
            getCommandExecutionState()[childIndecies[i]] = EXECUTED;
            result = getCommandList().get(childIndecies[i]).execute(context);
        }
        return result;
    }

    /**
     * The post process command for virtual chains.
     */
    protected final boolean virtualPostProcess(JXPathContext context, Exception exception, boolean result,
            int[] childIndecies) throws Exception {
        boolean handled = postProcessChildren(context, exception, childIndecies);

        if (exception != null && !handled) {
            throw exception;
        } else if (hasSaxExceptionFired()) {
            return true;
        } else {
            return result;
        }
    }

    /**
     * Calls post process of the specified indices.
     */
    protected final boolean postProcessChildren(JXPathContext context, Exception exception, int[] childIndecies) {
        boolean handled = false;

        for (int i = childIndecies.length - 1; i >= 0; i--) {
            if (getCommandExecutionState()[childIndecies[i]] == EXECUTED) {
                try {
                    Command command = getCommandList().get(childIndecies[i]);
                    if (command instanceof Filter) {
                        handled = ((Filter) command).postProcess(context, exception) || handled;
                    }
                } catch (Exception e) {
                    // ignore this exception.
                }
                getCommandExecutionState()[childIndecies[i]] = POST_PROCESSED;
            }
        }

        return handled;
    }

    /**
     * Turns a QName into its prefixed string representation.
     */
    protected static final String toPrefixedQName(QName qName) {
        if (qName.getPrefix() == null || "".equals(qName.getPrefix())) {
            return qName.getLocalPart();
        } else {
            return qName.getPrefix() + ":" + qName.getLocalPart();
        }
    }

    protected CommandHandler getContentHandler() {
        return ((CommandXmlReader) PipelineCommand.getPipelineConfig().getXmlReader()).getCommandHandler();
    }

    protected boolean hasSaxExceptionFired() {
        return saxExceptionTl.get() != null;
    }

    protected void registerSaxException(SAXException saxException) {
        saxExceptionTl.set(saxException);
    }
}