/**
* 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);
}
}