/**
* 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.framework.lifecycle;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import org.xchain.framework.jxpath.ScopedJXPathContextImpl;
/**
* <p>This class represents a lifecycle step that is defined using annotations.</p>
*
* <p>The start method for this lifecycle step can be defined using setStartMethod(Method). The start method should take at most one argument of type LifecycleContext. If the
* method throws an exception of a type other than LifecycleException, then the exception will be wrapped with a LifecycleException.</p>
*
* <p>The end method for this lifecycle step can be defined using setStopMethod(Method). The stop method should take at most one argument of type LifecycleContext. If the
* stop method throws an exception of a type other than RuntimeException, then the exception will be wrapped with a RuntimeException.</p>
*
* <p>If either the start or stop method is an instance method, then a static method for accessing the lifecycle class must be defined. This method will be called before
* calling an instance start or stop method, to get access to the lifecycle instance.</p>
*
* @author Christian Trimble
* @author John Trimble
*/
class AnnotationLifecycleStep
implements LifecycleStep
{
/** The start method for this lifecycle step. This method can be null. */
private Method startMethod;
/** The stop method for this lifecycle step. This method can be null. */
private Method stopMethod;
/** The method that returns the lifecycle singleton. If the start method or the stop method is an instance method, then this method must not be null. */
private Method lifecycleAccessor;
/** The prefix bindings to set on the ConfigDocumentContext for the start method. This map can be null. */
private Map<String, String> prefixMappings;
private QName qName;
/**
* <p>Constructs a new annotation lifecycle step that does not have a start method, a stop method, a lifecycle accessor, or a QName.</p>
*/
AnnotationLifecycleStep()
{
}
/**
* <p>Constructs a new annotation lifecycle step that does not have a start method, a stop method, or a lifecycle accessor.</p>
*/
AnnotationLifecycleStep(QName qName) {
this();
this.setQName(qName);
}
/**
* <p>Sets the QName for this step.</p>
*/
public void setQName(QName qName) { this.qName = qName; }
/**
* <p>Returns the QName for this step.</p>
*/
public QName getQName() { return this.qName; }
/**
* <p>Sets the start method for this annotated lifecycle step. The method set should take at most one argument of type LifecycleContext.</p>
*/
public void setStartMethod( Method startMethod ) { this.startMethod = startMethod; }
/**
* <p>Returns the start method.</p>
*/
public Method getStartMethod() { return this.startMethod; }
/**
* <p>Sets the start method for this annotated lifecycle step. The method set should take at most one argument of type LifecycleContext.</p>
*/
public void setStopMethod( Method stopMethod ) { this.stopMethod = stopMethod; }
/**
* <p>Returns the stop method for this annotated lifecycle step.</p>
*/
public Method getStopMethod() { return this.stopMethod; }
/**
* <p>Sets the method for accessing the lifecycle class instance. LifecycleAccessor methods must be static and take zero arguments. If either the start method or the stop methods is an
* instance method, then this method must be defined.</p>
*/
public void setLifecycleAccessor( Method lifecycleAccessor ) { this.lifecycleAccessor = lifecycleAccessor; }
/**
* <p>Returns the method for accessing the lifecycle class instance.</p>
*/
public Method getLifecycleAccessor() { return this.lifecycleAccessor; }
/**
* <p>Sets the prefix mappings to use with the DocumentConfigContext for the start method. This should be a mapping of xml prefixes to valid namespaces.</p>
*/
public void setStartMethodPrefixMappings(Map<String, String> prefixMappings) { this.prefixMappings = prefixMappings; }
/**
* <p>If the start method is defined, then this method will invoke the start method, otherwise this method does nothing.</p>
*
* @param context The current LifecycleContext.
*
* @throws LifecycleException If an exception is encountered starting this Lifecycle step.
*/
public void startLifecycle(LifecycleContext context, ConfigDocumentContext configDocContext)
throws LifecycleException
{
if( startMethod != null ) {
Map<String, String> savedPrefixMappings = null;
try {
// map prefixes
if( this.prefixMappings != null ) {
savedPrefixMappings = new HashMap<String, String>();
definePrefixMappings(configDocContext, prefixMappings, savedPrefixMappings);
}
invokeStepMethod(startMethod, lifecycleAccessor, context, configDocContext);
}
catch( InvocationTargetException ite ) {
if( ite.getCause() instanceof LifecycleException ) {
throw (LifecycleException)ite.getCause();
}
else if( ite.getCause() instanceof RuntimeException ) {
throw (RuntimeException)ite.getCause();
}
else if( ite.getCause() instanceof Error ) {
throw (Error)ite.getCause();
}
else {
throw new LifecycleException("The start method "+qName+" threw an unknown exception type.", ite);
}
}
catch( Exception e ) {
throw new LifecycleException("Could not execute lifecycle start step method "+qName+".", e);
} finally {
// unmap prefixes
if( this.prefixMappings != null ) {
undefinePrefixMappings(configDocContext, prefixMappings, savedPrefixMappings);
}
}
}
}
private static void definePrefixMappings(ScopedJXPathContextImpl context, Map<String,String> prefixMappings, Map<String,String> savedPrefixMappings)
{
for( Map.Entry<String, String> entry : prefixMappings.entrySet() ) {
String uri = context.getNamespaceURI(entry.getKey());
// save old mapping
if( uri != null )
savedPrefixMappings.put(entry.getKey(), uri);
// set prefix mapping
context.registerNamespace(entry.getKey(), entry.getValue());
}
}
private static void undefinePrefixMappings(ScopedJXPathContextImpl context, Map<String,String> prefixMappings, Map<String,String> savedPrefixMappings) {
// unregister prefix mappings
for( Map.Entry<String, String> entry : prefixMappings.entrySet() ) {
context.registerNamespace(entry.getKey(), null);
}
// restore saved mappings
for( Map.Entry<String, String> entry : savedPrefixMappings.entrySet() ) {
context.registerNamespace(entry.getKey(), entry.getValue());
}
}
/**
* <p>If the end method is defined, then this method will invoke the start method, otherwise this method does nothing.</p>
*
* @param context The current LifecycleContext.
*/
public void stopLifecycle(LifecycleContext context)
{
try {
if( stopMethod != null ) {
invokeStepMethod(stopMethod, lifecycleAccessor, context, null);
}
}
catch( InvocationTargetException ite ) {
if( ite.getCause() instanceof RuntimeException ) {
throw (RuntimeException)ite.getCause();
}
else if( ite.getCause() instanceof Error ) {
throw (Error)ite.getCause();
}
else {
throw new RuntimeException("The stop method "+qName+" threw an unknown exception type.", ite);
}
}
catch( RuntimeException re ) {
throw re;
}
catch( Exception e ) {
throw new RuntimeException("The lifecycle stop step "+qName+" threw an exception.", e);
}
}
private static Object invokeStepMethod(Method stepMethod, Method lifecycleAccessor, LifecycleContext lifecycleContext, ConfigDocumentContext configContext)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, LifecycleException
{
Object lifecycleObject = null;
if( !Modifier.isStatic(stepMethod.getModifiers()) ) {
if( lifecycleAccessor == null ) {
throw new LifecycleException("Cannot execute step method '"+stepMethod.getName()+"' of class '"+stepMethod.getDeclaringClass().getName()+"' without an accessor method.");
}
lifecycleObject = lifecycleAccessor.invoke(null);
if( lifecycleObject == null ) {
throw new LifecycleException("The accessor method '"+lifecycleAccessor+"' of class '"+lifecycleAccessor.getDeclaringClass().getName()+"' returned a null lifecycle object.");
}
}
Class<?>[] paramTypes = stepMethod.getParameterTypes();
Object[] values = new Object[paramTypes.length];
for( int i = 0; i < paramTypes.length; i++ ) {
if( paramTypes[i].isInstance(lifecycleContext) )
values[i] = lifecycleContext;
else if( paramTypes[i].isInstance(configContext) )
values[i] = configContext;
}
return stepMethod.invoke(lifecycleObject, values);
}
}