/**
* 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.util;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.ClassPool;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import javassist.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.regex.Matcher;
import javax.xml.namespace.QName;
import org.xchain.annotations.Attribute;
import org.xchain.annotations.AttributeType;
import org.xchain.annotations.PrefixMapping;
import static org.xchain.framework.util.AnnotationUtil.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to build engineered versions of commands and catalogs.
*
* TODO An explanation of why we're engineering classes.
*
* @author Christian Trimble
* @author Mike Moulton
* @author Devon Tackett
* @author Josh Kennedy
*/
public class EngineeringUtil
{
public static Logger log = LoggerFactory.getLogger( EngineeringUtil.class );
public static Pattern getMethodPattern = null;
static {
try {
getMethodPattern = Pattern.compile("\\A(?:get|iterate)(.+)\\Z");
}
catch( PatternSyntaxException pse ) {
if( log.isErrorEnabled() ) {
log.error("Could not create the pattern for matching get methods.", pse);
}
}
}
/**
* Create a ClassPool which can access all the classes available to the given ClassLoader.
*
* @param classLoader The ClassLoader to build the class path from.
*
* @return A ClassPool which can access classes available to the given ClassLoader.
*/
public static ClassPool createClassPool( ClassLoader classLoader )
{
ClassPool classPool = new ClassPool();
classPool.appendClassPath(new LoaderClassPath(classLoader));
return classPool;
}
/**
* Engineer the given command class.
*
* @param classPool The ClassPool to build from.
* @param ctClass The command class to engineer.
* @return The engineered command class.
*/
public static CtClass engineerCommand( ClassPool classPool, CtClass ctClass )
throws Exception
{
CtClass filterClass = getCtClass( classPool, "org.xchain.Filter");
CtClass locatableClass = getCtClass( classPool, "org.xchain.Locatable");
CtClass locatorClass = getCtClass( classPool, "org.xml.sax.Locator");
CtClass engineeredCommandClass = getCtClass( classPool, "org.xchain.EngineeredCommand");
CtClass qNameClass = getCtClass( classPool, "javax.xml.namespace.QName");
CtClass stringClass = getCtClass( classPool, "java.lang.String");
CtClass enumClass = getCtClass( classPool, "java.lang.Enum");
CtClass iteratorClass = getCtClass( classPool, "java.util.Iterator");
// create the engineered class
CtClass engineeredClass = classPool.makeClass(getEngineeredCommandName(ctClass), ctClass);
// if this class is not marked by the Engineered interface, then add it.
if( !engineeredClass.subtypeOf(engineeredCommandClass) ) {
engineeredClass.addInterface(engineeredCommandClass);
}
// add a list of namespace prefix mappings to the command, so that they can be set during the execute and postProcess methods.
engineeredClass.addField(CtField.make("protected java.util.Map prefixMap = new java.util.HashMap();", engineeredClass));
CtMethod prefixMappingGetterMethod = CtNewMethod.make("public java.util.Map getPrefixMap() { return prefixMap; }", engineeredClass);
engineeredClass.addMethod(prefixMappingGetterMethod);
engineeredClass.addField(CtField.make("protected java.util.Map attributeMap = new java.util.HashMap();", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public java.util.Map getAttributeMap() { return attributeMap; }", engineeredClass));
engineeredClass.addField(CtField.make("protected javax.xml.namespace.QName qName = null;", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public void setQName( javax.xml.namespace.QName qName ) { this.qName = qName; }", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public javax.xml.namespace.QName getQName() { return this.qName; }", engineeredClass));
engineeredClass.addField(CtField.make("protected String systemId = null;", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public void setSystemId( String systemId ) { this.systemId = systemId; }", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public String getSystemId() { return this.systemId; }", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public boolean isRegistered() { return this.qName != null && this.systemId != null; }", engineeredClass));
// add the locatable interface if it is missing.
if( !engineeredClass.subtypeOf(locatableClass) ) {
engineeredClass.addInterface(locatableClass);
}
// add or override locatable methods.
CtMethod setLocatorMethod = getMethod( engineeredClass, "setLocator", new CtClass[] { locatorClass } );
CtMethod getLocatorMethod = getMethod( engineeredClass, "getLocator", new CtClass[] {} );
CtField locatorField = getField( engineeredClass, "locator" );
// make sure the method getLocator() has the correct return type.
if( getLocatorMethod != null && !getLocatorMethod.getReturnType().subtypeOf(locatorClass) ) {
throw new Exception("The class '"+engineeredClass.getName()+"' is not compatable with the interface org.xchain.Locatable. The return type of getLocator() is incorrect.");
}
// make sure the method setLocator(Locator) has the correct return type.
if( setLocatorMethod != null && !setLocatorMethod.getReturnType().subtypeOf(CtClass.voidType) ) {
throw new Exception("The class '"+engineeredClass.getName()+"' is not compatable with the interface org.xchain.Locatable. The return type of setLocator( Locator locator ) is incorrect.");
}
// if we need to implement either the setLocator(Locator) method or the getLocator() methods, then we need to check the locator fields type.
if( (setLocatorMethod == null || getLocatorMethod == null) && locatorField != null && !locatorField.getType().subtypeOf(locatorClass) ) {
throw new Exception("The class '"+engineeredClass.getName()+"' has a locator field that is not of the type org.xml.sax.Locator.");
}
if( ( setLocatorMethod == null || getLocatorMethod == null ) && locatorField == null ) {
engineeredClass.addField(CtField.make("protected org.xml.sax.Locator locator = null;", engineeredClass));
}
if( setLocatorMethod == null ) {
engineeredClass.addMethod(CtNewMethod.make("public void setLocator( org.xml.sax.Locator locator ) { this.locator = locator; }", engineeredClass));
}
if( getLocatorMethod == null ) {
engineeredClass.addMethod(CtNewMethod.make("public org.xml.sax.Locator getLocator() { return this.locator; }", engineeredClass));
}
// Keep track of all the attributes for this engineered command.
Set<QName> attributeSet = new HashSet<QName>();
Map<QName, AttributeType> attributeTypeMap = new HashMap<QName, AttributeType>();
Map<QName, Boolean> attributeRequiredMap = new HashMap<QName, Boolean>();
Map<QName, CtClass> attributeJavaTypeMap = new HashMap<QName, CtClass>();
// add attribute fields to abstract methods.
for( CtMethod ctMethod : ctClass.getMethods() ) {
// if the ctMethod has the attribute annotations and is abstract, then add the needed methods.
if( hasAnnotation( ctMethod, Attribute.class ) ) {
// variables from the annotation.
String attributeLocalName = (String)getAnnotationValue( ctMethod, Attribute.class, "localName" );
String attributeUri = (String)getAnnotationValue( ctMethod, Attribute.class, "namespaceUri" );
String defaultValue = (String)getAnnotationValue( ctMethod, Attribute.class, "defaultValue" );
Boolean required = (Boolean)getAnnotationValue( ctMethod, Attribute.class, "required" );
// variables describing the return type.
boolean isPrimative = ctMethod.getReturnType().isPrimitive();
boolean isVoid = ctMethod.getReturnType().equals(CtClass.voidType);
boolean isBoolean = ctMethod.getReturnType().equals(CtClass.booleanType);
boolean isNumber = ctMethod.getReturnType().equals(CtClass.byteType) ||
ctMethod.getReturnType().equals(CtClass.charType) ||
ctMethod.getReturnType().equals(CtClass.doubleType) ||
ctMethod.getReturnType().equals(CtClass.floatType) ||
ctMethod.getReturnType().equals(CtClass.intType) ||
ctMethod.getReturnType().equals(CtClass.longType) ||
ctMethod.getReturnType().equals(CtClass.shortType);
boolean isTypeSpecified = ctMethod.getParameterTypes().length == 2;
// the type that will be used with ConvertUtils and JXPathContext
String typeExpression = isTypeSpecified ? "$2" : "$type";
boolean isIterateMethod = ctMethod.getName().startsWith("iterate") && ctMethod.getReturnType().subtypeOf(iteratorClass);
// add a default value of primative types.
if( defaultValue == null && isPrimative ) {
if( isBoolean ) { defaultValue = "false"; }
else if( isNumber ) { defaultValue = "("+ctMethod.getReturnType().getSimpleName()+")0;"; }
else if( isVoid ) { /* void does not get a default value */ }
else {
throw new IllegalStateException("The engineering code does not support the primative type '"+ctMethod.getReturnType().getSimpleName());
}
}
//
// Now we are going to engineer a method following this pattern:
//
// {
// // The name for this attribute.
// javax.xml.namespace.QName attributeName = new javax.xml.namespace.QName(ATTRIBUTE_NAMESPACE_URI, ATTRIBUTE_LOCAL_NAME);
//
// // The attribute value defined on this element.
// String attributeValue = (String)getAttributeMap().get(attributeName);
//
// // Do we need use the default?
// boolean doDefault = attributeValue == null;
//
// // The map that will hold the original mappings.
// java.util.Map originalPrefixMapping = null;
//
// // NOTE: If the attribute is required, but the attribute is not present, we need to throw an exception.
//
// // ASSERT: at this point, we are going to have a value to return, or fail with a type conversion exception.
// ATTRIBUTE_TYPE result;
// try {
// // define the prefix mappings for this commands element.
// org.xchain.framework.lifecycle.Execution.definePrefixMappings($1, this);
//
// // if we need a default and we have a default, then do the default mappings.
// if( doDefault ) {
// originalPrefixMapping = new java.util.HashMap();
//
// // record all of the current mappings for the default prefixes.
// originalPrefixMapping.put(DEFAULT_MAPPING_PREFIX, $1.getNamespaceURI(DEFAULT_MAPPING_PREFIX));
// ...
// // register the default namespace mappings.
// $1.registerNamespace(DEFAULT_MAPPING_PREFIX, DEFAULT_MAPPING_URI);
// ...
// attributeValue = defaultValue;
// }
//
// // Do the AttributeType specific lookup. This changes per attribute type.
// }
// catch( org.apache.commons.jxpath.JXPathException jxpe ) {
// Throwable cause = jxpe.getCause();
// if( cause == null ) {
// throw jxpe;
// }
// else if( cause instanceof RuntimeException ) {
// throw (RuntimeException)cause;
// }
// else if( cause instanceof THROWN_EXCEPTION ) {
// throw (THROWN_EXCEPTION)cause;
// }
// ..
// }
// finally {
// // if there are default mappings, include code to undo the mappings.
// if( doDefault && originalPrefixMapping != null ) {
// // there is one of there for each mapping.
// $1.registerNamespace(DEFAULT_MAPPING_PREFIX, (String)originalPrefixMapping.get(DEFAULT_MAPPING_PREFIX));
// ...
// }
// // undefine the prefix mappings for this commands element.
// org.xchain.framework.lifecycle.Execution.undefinePrefixMappings($1, this); }
// }
// return value;
QName attributeQName = new QName(attributeUri, attributeLocalName);
AttributeType attributeType = (AttributeType)getAnnotationValue( ctMethod, Attribute.class, "type" );
CtClass attributeJavaType = ctMethod.getReturnType();
// Add the attribute to the set of known attributes.
attributeSet.add(attributeQName);
attributeTypeMap.put(attributeQName, attributeType);
attributeJavaTypeMap.put(attributeQName, attributeJavaType);
attributeRequiredMap.put(attributeQName, required);
PrefixMapping[] defaultPrefixMappings = (PrefixMapping[])getAnnotationValue( ctMethod, Attribute.class, "defaultPrefixMappings" );
if( Modifier.isAbstract(ctMethod.getModifiers()) ) {
CtMethod engineeredMethod = CtNewMethod.copy( ctMethod, engineeredClass, null );
// Start making the method body.
StringBuffer methodBody = new StringBuffer();
methodBody.append("{\n");
methodBody.append(" // The name for this attribute.\n");
methodBody.append(" javax.xml.namespace.QName attributeName = new javax.xml.namespace.QName(")
.append(stringConstant(attributeUri)).append(", ").append(stringConstant(attributeLocalName)).append(");\n");
methodBody.append("\n");
methodBody.append(" // The attribute value defined on this element.\n");
methodBody.append(" String attributeValue = (String)getAttributeMap().get(attributeName);\n");
methodBody.append("\n");
methodBody.append(" // Do we need use the default?\n");
methodBody.append(" boolean doDefault = attributeValue == null;\n");
methodBody.append("\n");
methodBody.append(" // The map that will hold the original mappings.\n");
methodBody.append(" java.util.Map originalPrefixMapping = null;\n");
methodBody.append("\n");
// TODO: add code here to handle required.
// if the attributeValue is null, we are not in a primative, the default is "", and the attribute is not required, return null.
if( !isPrimative && "".equals(defaultValue)) {
methodBody.append(" if( attributeValue == null ) {\n");
methodBody.append(" return null;\n");
methodBody.append(" }\n");
methodBody.append("\n");
}
methodBody.append(" // ASSERT: at this point, we are going to have a value to return, or fail with a type conversion exception.\n");
methodBody.append("\n");
methodBody.append(" // This is the variable to hold the result.\n");
methodBody.append(" ").append(ctMethod.getReturnType().getName()).append(" result;\n");
methodBody.append("\n");
methodBody.append(" try {\n");
methodBody.append(" // define the prefix mappings for this commands element.\n");
methodBody.append(" org.xchain.framework.lifecycle.Execution.definePrefixMappings($1, this);\n");
methodBody.append("\n");
methodBody.append(" // if we need a default and we have a default, then do the default mappings.\n");
methodBody.append(" if( doDefault ) {\n");
methodBody.append(" originalPrefixMapping = new java.util.HashMap();\n");
methodBody.append("\n");
methodBody.append(" // record all of the current mappings for the default prefixes.\n");
for( PrefixMapping mapping : defaultPrefixMappings ) {
String escapedPrefix = stringConstant(mapping.prefix());
methodBody.append(" originalPrefixMapping.put(").append(escapedPrefix).append(", $1.getNamespaceURI(").append(escapedPrefix).append("));\n");
}
methodBody.append("\n");
methodBody.append(" // register the default namespace mappings.\n");
for( PrefixMapping mapping : defaultPrefixMappings ) {
String escapedPrefix = stringConstant(mapping.prefix());
String escapedUri = stringConstant(mapping.uri());
methodBody.append(" $1.registerNamespace(").append(escapedPrefix).append(", ").append(escapedUri).append(");\n");
}
methodBody.append("\n");
if( !"".equals(defaultValue) ) {
methodBody.append(" attributeValue = ").append(stringConstant(defaultValue)).append(";\n");
}
else if( isNumber ) {
methodBody.append(" attributeValue = \"0\";\n");
}
else if( isBoolean ) {
methodBody.append(" attributeValue = \"false\";\n");
}
methodBody.append(" }\n");
methodBody.append("\n");
// ASSERT: The top of the method definition is now done. The value of the attribute has now been looked up, defaulted, or the method would have caused an exception.
// It is now time to add the logic for the AttriuteType.
switch( attributeType ) {
//
// AttributeType.JXPATH_VALUE attributes follow one of two patterns...
// For methods that start with "iterate" and have a type of java.util.Iterator, the following code is used:
// result = ($r)$1.iterate(attributeValue);
//
// For all other methods, the following code is used:
// if( !TYPE.isAssignableFrom(REQUESTED_TYPE) {
// throw new IllegalArgumentException("Can not cast REQUESTED_TYPE into TYPE.");
// }
// result = ($r)$1.getValue(attributeValue, TYPE);
case JXPATH_VALUE:
if( isIterateMethod ) {
methodBody.append(" result = ($r)$1.iterate(attributeValue);\n");
}
else {
methodBody.append(" result = ($r)$1.getValue(attributeValue, ").append(typeExpression).append(");\n");
}
break;
case JXPATH_SELECT_NODES:
if( isIterateMethod ) {
methodBody.append(" result = ($r)$1.selectNodes(attributeValue).iterator();\n");
}
else {
methodBody.append(" return ($r)$1.selectNodes(attributeValue);\n");
}
break;
case JXPATH_SELECT_SINGLE_NODE:
methodBody.append(" return ($r)$1.selectSingleNode(attributeValue);\n");
break;
case JXPATH_POINTER:
methodBody.append(" result = ($r)$1.getPointer(attributeValue);\n");
break;
case JXPATH_ITERATE_POINTERS:
methodBody.append(" result = ($r)$1.iteratePointers(attributeValue);\n");
break;
case QNAME:
if( engineeredMethod.getReturnType().subtypeOf(qNameClass) ) {
methodBody.append(" result = org.xchain.framework.util.JXPathContextUtil.stringToQName($1, attributeValue);\n");
}
else if( engineeredMethod.getReturnType().subtypeOf(stringClass) ) {
methodBody.append(" result = org.xchain.framework.util.JXPathContextUtil.stringToQNameString($1, attributeValue);\n");
}
else {
throw new RuntimeException("QName attributes do not support the return type '"+engineeredMethod.getReturnType()+"'.");
}
break;
case LITERAL:
methodBody.append(" result = ($r)org.xchain.framework.util.JXPathContextUtil.convert((Object)attributeValue, ").append(typeExpression).append(");\n");
break;
case ATTRIBUTE_VALUE_TEMPLATE:
methodBody.append(" result = ($r)org.xchain.framework.util.JXPathContextUtil.convert((Object)org.xchain.framework.util.AttributesUtil.evaluateAttributeValueTemplate($1, attributeValue), ")
.append(typeExpression).append(");\n");
break;
default:
throw new RuntimeException("Unknown attribute type encountered.");
}
methodBody.append(" }\n");
// ASSERT: The method body now has a complete try block.
// JXPath wraps exceptions coming from functions in JXPathException's, we need to unwrap those, so that the caller get the exception that they expect.
methodBody.append(" catch( org.apache.commons.jxpath.JXPathException jxpe ) {\n");
methodBody.append(" Throwable cause = jxpe.getCause();\n");
methodBody.append(" // if there is no cause, then we cannot unwrap the exception.\n");
methodBody.append(" if( cause == null ) {\n");
methodBody.append(" throw jxpe;\n");
methodBody.append(" }\n");
methodBody.append(" // if this is a runtime exception, then we can throw it as a runtime exception.\n");
methodBody.append(" else if( cause instanceof RuntimeException ) {\n");
methodBody.append(" throw (RuntimeException)cause;\n");
methodBody.append(" }\n");
// for each exception that is declared to be thrown, we need to test the runnable and try to throw as that type.
for( CtClass thrownExceptionClass : ctMethod.getExceptionTypes() ) {
methodBody.append(" else if( cause instanceof ").append(thrownExceptionClass.getName()).append(" ) {\n");
methodBody.append(" throw (").append(thrownExceptionClass.getName()).append(")cause;\n");
methodBody.append(" }\n");
}
methodBody.append(" // we could not cast the cause, so leave the exception wrapped.\n");
methodBody.append(" else {\n");
methodBody.append(" throw jxpe;\n");
methodBody.append(" }\n");
methodBody.append(" }\n");
// ASSERT: Any exceptions thrown while evaluating an attribute have been unwrapped if they could.
methodBody.append(" finally {\n");
methodBody.append(" // if there are default mappings, include code to undo the mappings.\n");
methodBody.append(" if( doDefault && originalPrefixMapping != null ) {\n");
methodBody.append(" // there is one of there for each mapping.\n");
for( PrefixMapping mapping : defaultPrefixMappings ) {
String escapedPrefix = stringConstant(mapping.prefix());
methodBody.append(" $1.registerNamespace(").append(escapedPrefix).append(", (String)originalPrefixMapping.get(").append(escapedPrefix).append("));\n");
}
methodBody.append(" }\n");
methodBody.append(" // undefine the prefix mappings for this commands element.\n");
methodBody.append(" org.xchain.framework.lifecycle.Execution.undefinePrefixMappings($1, this);\n");
methodBody.append(" }\n");
methodBody.append(" return result;\n");
methodBody.append("}\n");
//System.out.println(methodBody.toString());
//Thread.currentThread().sleep(1000);
engineeredMethod.setBody(methodBody.toString());
engineeredMethod.setModifiers( Modifier.clear( ctMethod.getModifiers(), Modifier.ABSTRACT ) );
engineeredClass.addMethod(engineeredMethod);
}
// test to see if the class has a "has" method.
Matcher matcher = getMethodPattern.matcher(ctMethod.getName());
if( matcher.find() ) {
String hasMethodName = "has"+matcher.group(1);
try {
CtMethod hasMethod = ctClass.getMethod( hasMethodName, "()Z");
if( Modifier.isAbstract(hasMethod.getModifiers()) ) {
CtMethod engineeredHasMethod = CtNewMethod.copy( hasMethod, engineeredClass, null );
// TODO: should we return true if there is a default value?
engineeredHasMethod.setBody(
"{"+
"return getAttributeMap().containsKey(new javax.xml.namespace.QName(\""+attributeUri+"\", \""+attributeLocalName+"\"));"+
"}"
);
engineeredHasMethod.setModifiers( Modifier.clear( hasMethod.getModifiers(), Modifier.ABSTRACT ) );
engineeredClass.addMethod(engineeredHasMethod);
}
}
catch( NotFoundException nfe ) {
// do nothing, we do not need this method.
}
}
}
}
// Add all static fields and value.
// Add the static attributes.
engineeredClass.addField(CtField.make("protected static java.util.Set attributeSet = new java.util.HashSet();", engineeredClass));
engineeredClass.addField(CtField.make("protected static java.util.Map attributeDetailMap = new java.util.HashMap();", engineeredClass));
// Add the class initializer for the static attributes.
StringBuilder ib = new StringBuilder();
ib.append("{");
ib.append("javax.xml.namespace.QName attributeQName = null;");
ib.append("org.xchain.AttributeDetail attributeDetail = null;");
for(QName attributeName : attributeSet) {
AttributeType attributeType = attributeTypeMap.get(attributeName);
CtClass attributeJavaType = attributeJavaTypeMap.get(attributeName);
ib.append("attributeQName = new javax.xml.namespace.QName(\"" + attributeName.getNamespaceURI() + "\", \"" + attributeName.getLocalPart() + "\");");
ib.append("attributeSet.add(attributeQName);");
ib.append("attributeDetail = new org.xchain.AttributeDetail(attributeQName, org.xchain.annotations.AttributeType."+attributeType.name()+", "+attributeJavaType.getName()+".class, "+attributeRequiredMap.get(attributeName)+");");
ib.append("attributeDetailMap.put(attributeQName, attributeDetail);");
}
ib.append("attributeSet = java.util.Collections.unmodifiableSet(attributeSet);");
ib.append("attributeDetailMap = java.util.Collections.unmodifiableMap(attributeDetailMap);");
ib.append("}");
engineeredClass.makeClassInitializer().insertAfter(ib.toString());
// Add the instance accessors for the attribute set and the attribute type set.
engineeredClass.addMethod(CtNewMethod.make("public java.util.Set getAttributeSet() { return attributeSet; }", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public java.util.Map getAttributeDetailMap() { return attributeDetailMap; }", engineeredClass));
// find the original execute method.
// create a new method that sets the mappings into the context and the current system id.
StringBuilder newExecuteMethod = new StringBuilder();
// code to insert the mappings.
newExecuteMethod.append("public boolean execute(org.apache.commons.jxpath.JXPathContext context) throws java.lang.Exception {");
newExecuteMethod.append(" boolean createLocalContext = isRegistered();");
newExecuteMethod.append(" boolean inThread = org.xchain.framework.lifecycle.ThreadLifecycle.getInstance().inThread();");
newExecuteMethod.append(" boolean inExecution = org.xchain.framework.lifecycle.Execution.inExecution();");
newExecuteMethod.append(" boolean result = false;");
newExecuteMethod.append(" org.xchain.framework.lifecycle.ThreadContext threadContext = null;");
newExecuteMethod.append(" if( !inThread ) {");
newExecuteMethod.append(" threadContext = new org.xchain.framework.lifecycle.ThreadContext();");
newExecuteMethod.append(" org.xchain.framework.lifecycle.ThreadLifecycle.getInstance().startThread(threadContext);");
newExecuteMethod.append(" }");
newExecuteMethod.append(" try {");
newExecuteMethod.append(" if( !inExecution ) {");
newExecuteMethod.append(" org.xchain.framework.lifecycle.Execution.startExecution(context);");
newExecuteMethod.append(" }");
newExecuteMethod.append(" context = org.xchain.framework.lifecycle.Execution.startCommandExecute(this, context);");
newExecuteMethod.append(" Exception exception = null;");
newExecuteMethod.append(" try {");
newExecuteMethod.append(" result = super.execute(context);");
newExecuteMethod.append(" }");
newExecuteMethod.append(" catch( Exception e ) {");
newExecuteMethod.append(" org.xchain.framework.lifecycle.Execution.exceptionThrown(this, e);");
newExecuteMethod.append(" exception = e;");
newExecuteMethod.append(" throw e;");
newExecuteMethod.append(" }");
newExecuteMethod.append(" finally {");
newExecuteMethod.append(" context = org.xchain.framework.lifecycle.Execution.endCommandExecute(this, context);");
// if this is a filter, then after the first execute call, we need to make the post process call that would have been
// made by our containing chain.
if( engineeredClass.subtypeOf(filterClass) ) {
newExecuteMethod.append(" if( !inExecution ) {");
newExecuteMethod.append(" context = org.xchain.framework.lifecycle.Execution.startCommandPostProcess( this, context );");
newExecuteMethod.append(" try {");
newExecuteMethod.append(" boolean handled = super.postProcess(context, exception);");
newExecuteMethod.append(" if( handled == true ) {");
newExecuteMethod.append(" org.xchain.framework.lifecycle.Execution.exceptionHandled(this, exception);");
newExecuteMethod.append(" }");
newExecuteMethod.append(" }");
newExecuteMethod.append(" finally {");
newExecuteMethod.append(" org.xchain.framework.lifecycle.Execution.endCommandPostProcess(this, context);");
newExecuteMethod.append(" org.xchain.framework.lifecycle.Execution.endExecution();");
newExecuteMethod.append(" }");
newExecuteMethod.append(" }");
}
else {
newExecuteMethod.append(" if( !inExecution ) {");
newExecuteMethod.append(" org.xchain.framework.lifecycle.Execution.endExecution();");
newExecuteMethod.append(" }");
}
newExecuteMethod.append(" }");
newExecuteMethod.append(" }");
newExecuteMethod.append(" finally {");
newExecuteMethod.append(" if( !inThread ) {");
newExecuteMethod.append(" org.xchain.framework.lifecycle.ThreadLifecycle.getInstance().stopThread(threadContext);");
newExecuteMethod.append(" }");
newExecuteMethod.append(" }");
newExecuteMethod.append(" return result;");
newExecuteMethod.append("}");
engineeredClass.addMethod(CtNewMethod.make(newExecuteMethod.toString(), engineeredClass));
if( engineeredClass.subtypeOf(filterClass) ) {
StringBuilder newPostProcessMethod = new StringBuilder();
// code to insert the mappings.
newPostProcessMethod.append("public boolean postProcess( org.apache.commons.jxpath.JXPathContext context, Exception exception ) {");
newPostProcessMethod.append(" boolean handled = false;");
newPostProcessMethod.append(" if( org.xchain.framework.lifecycle.Execution.inExecution() ) {");
newPostProcessMethod.append(" context = org.xchain.framework.lifecycle.Execution.startCommandPostProcess( this, context );");
newPostProcessMethod.append(" try {");
newPostProcessMethod.append(" handled = super.postProcess(context, exception);");
newPostProcessMethod.append(" if( handled == true ) {");
newPostProcessMethod.append(" org.xchain.framework.lifecycle.Execution.exceptionHandled(this, exception);");
newPostProcessMethod.append(" }");
newPostProcessMethod.append(" return handled;");
newPostProcessMethod.append(" }");
newPostProcessMethod.append(" finally {");
newPostProcessMethod.append(" org.xchain.framework.lifecycle.Execution.endCommandPostProcess(this, context);");
newPostProcessMethod.append(" }");
newPostProcessMethod.append(" }");
newPostProcessMethod.append(" return handled;");
newPostProcessMethod.append("}");
engineeredClass.addMethod(CtNewMethod.make(newPostProcessMethod.toString(), engineeredClass));
}
return engineeredClass;
}
/**
* Build a unique engineered command name from the given CtClass.
* @param originalClass The class to build a unique engineered command name from.
* @return A unique engineered name for the given command CtClass.
*/
public static String getEngineeredCommandName( CtClass originalClass )
{
return originalClass.getName()+"Engineered";
}
/**
* Find the CtMethod on the given class.
* @param ctClass The class to find the method on.
* @param name The name of the method to find.
* @param params The array of parameters that the method will use.
* @return The indicated method or null if the method could not be found.
*/
public static CtMethod getMethod( CtClass ctClass, String name, CtClass[] params )
{
try {
return ctClass.getDeclaredMethod(name, params);
}
catch( NotFoundException nfe ) {
return null;
}
}
/**
* Find the field on the given class.
* @param ctClass The class on which to find the field.
* @param name The name of the field.
* @return The requested field or null if the field could not be found.
*/
public static CtField getField( CtClass ctClass, String name )
{
try {
return ctClass.getField(name);
}
catch( NotFoundException nfe ) {
return null;
}
}
/**
* Find the class in the pool for the given name.
* @param classPool The ClassPool to search through.
* @param name The name of the class to find.
* @return The requested class. If the requested class could not be found a runtime exception will be throw.
*/
public static CtClass getCtClass( ClassPool classPool, String name )
{
try {
return classPool.get(name);
}
catch( NotFoundException nfe ) {
throw new RuntimeException("The engineering util expected to find the class '"+name+"', but it could not be found.", nfe);
}
}
/**
* Determine if the given class is a subtype of the given type.
* @param ctClass The class to check.
* @param subtype The subtype to check against.
* @return True if the given class is a subtype of the given subtype. This is also true if the given class is the same as the given subtype.
*/
public static boolean subtypeOf( CtClass ctClass, CtClass subtype )
{
try {
return ctClass.subtypeOf(subtype);
}
catch( NotFoundException nfe ) {
throw new RuntimeException("An exception was thrown while testing for a subclass.", nfe);
}
}
/**
* Engineer the given catalog class.
*
* @param classPool The ClassPool to build from.
* @param ctClass The catalog class to engineer.
* @return The engineered catalog class.
*/
public static CtClass engineerCatalog( ClassPool classPool, CtClass ctClass )
throws Exception
{
CtClass engineeredCatalogClass = getCtClass( classPool, "org.xchain.EngineeredCatalog");
// create the engineered class
CtClass engineeredClass = classPool.makeClass(getEngineeredCommandName(ctClass), ctClass);
// if this class is not marked by the Engineered interface, then add it.
if( !engineeredClass.subtypeOf(engineeredCatalogClass) ) {
engineeredClass.addInterface(engineeredCatalogClass);
}
// add the class loader.
engineeredClass.addField(CtField.make("protected java.lang.ClassLoader classLoader = null;", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public java.lang.ClassLoader getClassLoader() { return this.classLoader; }", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public void setClassLoader(java.lang.ClassLoader classLoader) { this.classLoader = classLoader; }", engineeredClass));
// add the engineered system id.
engineeredClass.addField(CtField.make("protected java.lang.String systemId = null;", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public java.lang.String getSystemId() { return this.systemId; }", engineeredClass));
engineeredClass.addMethod(CtNewMethod.make("public void setSystemId(java.lang.String systemId) { this.systemId = systemId; }", engineeredClass));
return engineeredClass;
}
public static String stringConstant( String source )
{
// is the source is null, return null.
if( source == null ) {
return "((String)null)";
}
// the string buffer for the result.
StringBuilder builder = new StringBuilder();
// the initial double quote.
builder.append('\"');
// escape special characters in the string.
source = source.replaceAll("\\\\", "\\\\\\\\");
source = source.replaceAll("\\\"", "\\\\\"");
source = source.replaceAll("\\\'", "\\\\\'");
source = source.replaceAll("\r", "\\\\r");
source = source.replaceAll("\t", "\\\\t");
source = source.replaceAll("\b", "\\\\b");
source = source.replaceAll("\n", "\\\\n");
source = source.replaceAll("\f", "\\\\f");
builder.append(source);
// terminating double quote.
builder.append('\"');
return builder.toString();
}
}