/**
* 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 org.xchain.framework.util.EngineeringUtil;
import org.xchain.annotations.Component;
import org.xchain.annotations.Element;
import org.xchain.annotations.Function;
import org.xchain.annotations.Namespace;
import org.xchain.annotations.Optional;
import org.xchain.framework.util.AnnotationUtil;
import org.xchain.framework.util.ComponentUtil;
import org.xchain.framework.scanner.AbstractScanner;
import org.xchain.framework.scanner.MarkerResourceLocator;
import org.xchain.framework.scanner.ScanException;
import org.xchain.framework.scanner.ScanNode;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.regex.Pattern;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.bytecode.ClassFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A scanner that searches the lifecycle context looking for commands and catalogs, creates engineered versions of those classes, and loads them into the context.
*
* @author Christian Trimble
* @author Mike Moulton
* @author Devon Tackett
* @author John Trimble
* @author Josh Kennedy
*/
public class ClassScanner
extends AbstractScanner
{
public static Logger log = LoggerFactory.getLogger(ClassScanner.class);
protected LifecycleContext context = null;
protected ClassPool classPool = null;
protected CtClass catalogCtClass = null;
protected CtClass commandCtClass = null;
public ClassScanner( LifecycleContext context )
{
super( new MarkerResourceLocator("META-INF/xchain.xml"), context.getClassLoader() );
this.context = context;
}
/**
* Starts the scanning of the lifecycle context.
*/
public void scan()
{
try {
classPool = EngineeringUtil.createClassPool(classLoader);
catalogCtClass = classPool.get("org.xchain.Catalog");
commandCtClass = classPool.get("org.xchain.Command");
super.scan();
}
catch( Exception e ) {
e.printStackTrace();
log.warn("Unexpected Exception", e);
}
finally {
classPool = null;
}
}
@Override
public void scanNode(ScanNode node)
throws ScanException
{
try {
// Handle all class files.
if( isLoadableClassFile(node.getResourceName()) ) {
// Get the name of the class.
String className = toClassName(node);
// Get the class from teh class pool
CtClass scannedCtClass = classPool.get(className);
// Check whether the class has an Element annotation.
if( AnnotationUtil.hasAnnotation(scannedCtClass, Element.class) || AnnotationUtil.hasAnnotation(scannedCtClass, Component.class) || hasFunctionsAnnotation(scannedCtClass) ) {
String namespaceUri = null;
if( AnnotationUtil.hasAnnotation(scannedCtClass, LifecycleClass.class) ) {
namespaceUri = (String)AnnotationUtil.getAnnotationValue(scannedCtClass.getClassFile(), LifecycleClass.class, "uri");
}
else {
try {
// figure out what namespace we are in.
String packageName = scannedCtClass.getPackageName();
// Get the package level information for the class.
CtClass packageCtClass = classPool.get(packageName+".package-info");
namespaceUri = (String)AnnotationUtil.getAnnotationValue(packageCtClass.getClassFile(), Namespace.class, "uri");
}
catch( Exception e ) {
if( log.isDebugEnabled() ) {
log.debug("Could not load package-info.class for "+scannedCtClass.getName()+".");
}
}
}
if( namespaceUri != null ) {
// Try to find the namespace context in the current context.
NamespaceContext namespaceContext = context.getNamespaceContextMap().get(namespaceUri);
if( namespaceUri != null && namespaceContext == null ) {
// Namespace not found, create a new namespace context.
namespaceContext = new NamespaceContext(namespaceUri);
context.getNamespaceContextMap().put(namespaceUri, namespaceContext);
if( log.isInfoEnabled() ) {
log.info("Found xchain uri '"+namespaceUri+"'.");
}
}
if( namespaceContext != null ) {
// Check if the scanned class is optional.
boolean isOptional = AnnotationUtil.hasAnnotation(scannedCtClass, Optional.class);
try {
if( scannedCtClass.subtypeOf(catalogCtClass) ) {
// The class is a catalog, engineer as such.
CtClass engineeredCtClass = EngineeringUtil.engineerCatalog(classPool, scannedCtClass);
// scan the class for static methods with Function annotations.
namespaceContext.getCatalogList().add(classPool.toClass(engineeredCtClass, context.getClassLoader()));
if( hasFunctionsAnnotation(scannedCtClass) ) {
Class scannedClass = Thread.currentThread().getContextClassLoader().loadClass(className);
scanStaticFunctions(namespaceContext, scannedClass);
}
for( CtClass nestedCtClass : engineeredCtClass.getNestedClasses() ) {
classPool.toClass( nestedCtClass, context.getClassLoader() );
// TODO: What are the class loading implications of this?
// scan the class for static methods with Function annotations.
//scanStaticFunctions(namespaceContext, nestedCtClass);
//scanInstanceFunctions(namespaceContext, nestedCtClass);
}
}
else if( scannedCtClass.subtypeOf(commandCtClass) ) {
// The class is a command, engineer as such.
CtClass engineeredCtClass = EngineeringUtil.engineerCommand(classPool, scannedCtClass);
namespaceContext.getCommandList().add(classPool.toClass(engineeredCtClass, context.getClassLoader()));
// scan the class for static methods with Function annotations.
if( hasFunctionsAnnotation(scannedCtClass) ) {
Class scannedClass = Thread.currentThread().getContextClassLoader().loadClass(className);
scanStaticFunctions(namespaceContext, scannedClass);
}
for( CtClass nestedCtClass : engineeredCtClass.getNestedClasses() ) {
if( log.isDebugEnabled() ) log.debug("Adding class "+nestedCtClass.getName());
classPool.toClass( nestedCtClass, context.getClassLoader() );
// TODO: what are the class loading implications of this?
//scanStaticFunctions(namespaceContext, nestedCtClass);
//scanInstanceFunctions(namespaceContext, nestedCtClass);
}
}
else if ( AnnotationUtil.hasAnnotation(scannedCtClass, Component.class)) {
// The class is an component.
// TODO
namespaceContext.getComponentMap().put(AnnotationUtil.getAnnotationValue(scannedCtClass.getClassFile(), Component.class, "localName").toString(),
ComponentUtil.createAnalysis(Thread.currentThread().getContextClassLoader().loadClass(className)));
// scan the class for static and instance methods with Function annotations.
if( hasFunctionsAnnotation(scannedCtClass) ) {
Class scannedClass = Thread.currentThread().getContextClassLoader().loadClass(className);
scanStaticFunctions(namespaceContext, scannedClass);
scanInstanceFunctions(namespaceContext, scannedClass);
}
}
else {
// scan the class for static and instance methods with Function annotations/
if( hasFunctionsAnnotation(scannedCtClass) ) {
Class scannedClass = Thread.currentThread().getContextClassLoader().loadClass(className);
scanStaticFunctions(namespaceContext, scannedClass);
scanInstanceFunctions(namespaceContext, scannedClass);
scanConstructorFunctions(namespaceContext, scannedClass);
}
}
} catch (NoClassDefFoundError error) {
if (isOptional)
log.info("Class {} has missing dependencies but is marked optional.", scannedCtClass.getName());
else
throw error;
}
// load any inner classes from the source file.
}
}
else {
if( log.isWarnEnabled() ) {
log.warn("Skipping class '"+scannedCtClass.getName()+"' because its package does not specify a namespace uri.");
}
}
}
}
}
catch( Exception e ) {
e.printStackTrace();
log.error("Unhandled Exception", e);
}
}
public boolean hasFunctionsAnnotation( CtClass ctClass )
throws Exception
{
for( CtMethod ctMethod : ctClass.getMethods() ) {
if( AnnotationUtil.hasAnnotation(ctMethod, org.xchain.annotations.Function.class) ) {
return true;
}
}
for( CtConstructor ctConstructor : ctClass.getConstructors() ) {
if( AnnotationUtil.hasAnnotation(ctConstructor, org.xchain.annotations.Function.class) ) {
return true;
}
}
return false;
}
public Method getSingletonAccessor( Class scannedClass )
{
for( Method method : scannedClass.getDeclaredMethods() ) {
//try {
if( java.lang.reflect.Modifier.isStatic(method.getModifiers()) && AnnotationUtil.hasAnnotation(method, org.xchain.framework.lifecycle.LifecycleAccessor.class) ) {
return method;
}
//}
//catch( Exception e ) {
//if( log.isDebugEnabled() ) {
//log.debug("Could not scan method '"+method.toGenericString()+" in class "+scannedClass.getName()+".", e);
//}
//}
}
return null;
}
public void scanStaticFunctions( NamespaceContext namespaceContext, Class scannedClass )
{
NamespaceFunctionLibrary functionLibrary = namespaceContext.getFunctionLibrary();
// iterate all of the static methods in this class.
for( Method method : scannedClass.getDeclaredMethods() ) {
org.xchain.annotations.Function functionAnnotation = method.getAnnotation(org.xchain.annotations.Function.class);
if( functionAnnotation != null && java.lang.reflect.Modifier.isStatic(method.getModifiers()) ) {
// get the value of the local name.
String localName = functionAnnotation.localName();
functionLibrary.addStaticFunction(localName, scannedClass, method.getName());
}
else if( java.lang.reflect.Modifier.isStatic(method.getModifiers()) ) {
if( log.isDebugEnabled() ) {
log.debug("Skipping method '"+method.toString()+"' because it does not have a Function annotation.");
}
}
}
}
public void scanInstanceFunctions( NamespaceContext namespaceContext, Class scannedClass )
{
NamespaceFunctionLibrary functionLibrary = namespaceContext.getFunctionLibrary();
// get the singleton accessor if it is defined.
Method singletonAccessor = getSingletonAccessor(scannedClass);
for( Method method : scannedClass.getDeclaredMethods() ) {
org.xchain.annotations.Function functionAnnotation = method.getAnnotation(org.xchain.annotations.Function.class);
if( functionAnnotation != null && !java.lang.reflect.Modifier.isStatic(method.getModifiers()) ) {
// get the value of the local name.
String localName = functionAnnotation.localName();
if( singletonAccessor != null ) {
functionLibrary.addSingletonInstanceFunction(localName, scannedClass, method.getName(), singletonAccessor);
}
else {
functionLibrary.addInstanceFunction(localName, scannedClass, method.getName());
}
}
else if( !java.lang.reflect.Modifier.isStatic(method.getModifiers()) ) {
if( log.isDebugEnabled() ) {
log.debug("Skipping method '"+method.toString()+"' because it does not have a Function annotation.");
}
}
}
}
public void scanConstructorFunctions( NamespaceContext namespaceContext, Class scannedClass )
{
NamespaceFunctionLibrary functionLibrary = namespaceContext.getFunctionLibrary();
for( Constructor constructor : scannedClass.getConstructors() ) {
// Note: This cast should not be necessary, but Eclipse complains without it.
org.xchain.annotations.Function functionAnnotation = (Function) constructor.getAnnotation(org.xchain.annotations.Function.class);
if( functionAnnotation != null ) {
// get the value of the local name.
String localName = functionAnnotation.localName();
functionLibrary.addConstructorFunction(localName, scannedClass);
}
else {
if( log.isDebugEnabled() ) {
log.debug("Skipping a constructor for '"+scannedClass.getName()+"' because it does not have a Function annotation.");
}
}
}
}
}