package com.horstmann.violet.framework.injection.bean;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Manioc is a very very (VERY!) light and basic injection framework. <br/>
* IT ONLY CONTAINS ONE JAVA SOURCE FILE!!!. Just copy it into your project. <br/>
* <br/>
* <br/>
* Release note : <br/>
* Version 0.0.1 (07-21-2011) : <br/>
* + contains a BeanFactory and a BeanInjector<br/>
* + can manage beans annotated with \@ManagedBean<br/>
* + supports injection on fields with \@InjectedBean<br/>
* + supports bean construction on specific constructor or factory method with \@Construct
* <br/>
* + automatically injects beans on constructor and factory method without
* specifying any annotation<br/>
* + supports \@PostConstruct and \@PreDestroy<br/>
* + supports multiple injection contexts with hierarchy with
* \@ManagedBean(context)<br/>
* + supports bean scope (singleton and prototype) with \@ManagedBean(scope)<br/>
* + supports injection by annotation on existing beans<br/>
* + supports manual registration of pre-construct beans on the bean factory
* with BeanFactory.register()<br/>
*
* Version 0.0.2 (10-07-2011) : <br/>
* + bug fix on manual bean registration with interface type <br/>
*
* Version 0.0.3 (01-20-2014) : <br/>
* + supports injection inside webapp by associating BeanFactory and BeanInjector with current classloader
*
* @author Alexandre de Pellegrin
*
*/
public class ManiocFramework {
/**
* This injector is made to perform dependency injection on objects. This
* class was inspired from the excellent Apache Wicket framework.
*
* @author Alexandre de Pellegrin
*
*/
public static class BeanInjector {
/**
* Injector instances per classloader
*/
private static Map<ClassLoader, BeanInjector> instances = new HashMap<ClassLoader, BeanInjector>();
/**
* Singleton constructor
*/
private BeanInjector() {
// Singleton pattern
}
/**
* @return the only object instance
*/
public static BeanInjector getInjector() {
ClassLoader currentClassLoader = BeanInjector.class.getClassLoader();
if (!instances.containsKey(currentClassLoader)) {
BeanInjector newInstance = new BeanInjector();
instances.put(currentClassLoader, newInstance);
}
return instances.get(currentClassLoader);
}
/**
* Injects beans on fields annotated with \@InjectedBean <br/>
* For managed beans, you can specified the application context you want <br>
* directly in the \@ManagedBean annotation.
*
* @param o
*/
public void inject(Object o) {
Class<? extends Object> implementationType = o.getClass();
ManagedBean annotation = implementationType.getAnnotation(ManagedBean.class);
if (annotation != null) {
Class<? extends DefaultApplicationContext> context = annotation.applicationContext();
this.inject(o, context);
return;
}
if (annotation == null) {
this.inject(o, DefaultApplicationContext.class);
}
}
/**
* Injects beans in fields annotated with \@InjectedBean. <br/>
* Beans are taken from the specified application context. </br>
*
* @param o
* @param context
*/
public void inject(Object o, Class<? extends DefaultApplicationContext> context) {
List<Class<?>> classAndSuperClasses = getClassAndSuperClasses(o);
for (Class<?> aClass : classAndSuperClasses) {
// Injects on fields (only if they haven't any value set)
for (Field aField : aClass.getDeclaredFields()) {
InjectedBean propertyAnnotation = aField.getAnnotation(InjectedBean.class);
if (propertyAnnotation != null) {
aField.setAccessible(true);
if (!isFieldNUll(aField, o)) {
continue;
}
Class<?> beanType = aField.getType();
boolean isInterface = beanType.isInterface();
if (isInterface) {
boolean isImplementationFound = false;
// Step 1 : take default implementation
ImplementedBy defaultImplementationAnnotation = beanType.getAnnotation(ImplementedBy.class);
if (defaultImplementationAnnotation != null) {
beanType = defaultImplementationAnnotation.value();
isImplementationFound = true;
}
// Step 2 : overwrite it by another one if declared
// directly on the field
if (!Object.class.equals(propertyAnnotation.implementation())) {
beanType = propertyAnnotation.implementation();
isImplementationFound = true;
}
// Step 3 : look for default impletation in factory
if (BeanFactory.getFactory(context).getBean(beanType) != null) {
isImplementationFound = true;
}
if (!isImplementationFound) {
throw new ManiocException("Unable to find which implementation to use for a bean of type " + aField.getType().getName() + " . Application context is " + context.getSimpleName());
}
}
Object beanToInject = BeanFactory.getFactory(context).getBean(beanType);
if (beanToInject == null) {
if (isInterface) {
throw new ManiocException("Unable to inject a bean which implements " + aField.getType().getName() + " . Implementation excepted is " + beanType.getName() + ". Application context is " + context.getSimpleName());
}
if (!isInterface) {
throw new ManiocException("Unable to inject a bean of type " + beanType.getName() + " . No such bean found. Application context is " + context.getSimpleName());
}
}
try {
aField.set(o, beanToInject);
} catch (IllegalArgumentException e) {
throw new ManiocException("Error while setting field value of bean managed by the BeanFactory Application context is " + context.getSimpleName(), e);
} catch (IllegalAccessException e) {
throw new ManiocException("Error while setting field value of bean managed by the BeanFactory Application context is " + context.getSimpleName(), e);
}
}
}
}
}
/**
* Takes an objet and returns its class and all its inherited communication
*
* @param o
* @return
*/
private List<Class<?>> getClassAndSuperClasses(Object o) {
List<Class<?>> result = new ArrayList<Class<?>>();
List<Class<?>> fifo = new ArrayList<Class<?>>();
fifo.add(o.getClass());
while (!fifo.isEmpty()) {
Class<?> aClass = fifo.remove(0);
;
Class<?> aSuperClass = aClass.getSuperclass();
if (aSuperClass != null) {
fifo.add(aSuperClass);
}
result.add(aClass);
}
return result;
}
/**
* Checks if a field is empty (null value)
*
* @param aField
* @param o
* @return true if null
*/
private boolean isFieldNUll(Field aField, Object o) {
try {
Object currentValue = aField.get(o);
if (currentValue == null) {
return true;
}
} catch (IllegalArgumentException e1) {
throw new ManiocException("Error while getting field value of bean managed by the BeanFactory. Field = " + aField.getName(), e1);
} catch (IllegalAccessException e1) {
throw new ManiocException("Error while getting field value of bean managed by the BeanFactory. Field = " + aField.getName(), e1);
}
return false;
}
}
/**
* The factory automatically creates bean instances based on annotations
* (\@ManagedBean). If a bean cannot be created automatically, don't worry.
* You can register it manually to the factory. <br/>
* <br/>
* Because applications often contain many beans, you can reduce theirs
* visibility of inside your applications, by limiting a factory scope to a
* specific context. To create a context, just create a class that extends
* DefaultApplicationContext. Then, specify it in your class annotations
* (\@ManagedBean) or when you call BeanFactory.getFactory(). <br/>
* <br/>
* Don't forget that you can create hierarchy of contexts (example :
* serviceContext that extends guiContext that extends
* DefaultApplicationContext). That a nice way to offers java services to
* graphical user interface but to avoid use of gui bean from the service
* layer.
*
* @author Alexandre de Pellegrin
*
*/
public static class BeanFactory {
/** Contains all factories associated with a classloader */
private static Map<ClassLoader, Map<Class<? extends DefaultApplicationContext>, BeanFactory>> factoryInstancesPerClassloader = new HashMap<ClassLoader, Map<Class<? extends DefaultApplicationContext>, BeanFactory>>();
/** Contains all all the beans managed by all the factories associated with a classloader */
private static Map<ClassLoader, Map<Object, Class<? extends DefaultApplicationContext>>> managedBeansPerClassloader = new HashMap<ClassLoader, Map<Object, Class<? extends DefaultApplicationContext>>>();
/** Keeps references to singleton (beans can be singleton or protoypes */
private Map<Class<?>, Object> singletonsMap = new HashMap<Class<?>, Object>();
/** Current factory context */
private Class<? extends DefaultApplicationContext> context;
/** This flag indicates if Manioc has to finalize managed beans or not on JVM shutdown */
private static boolean isFinalizeBeansDelegated = false;
/**
* Listen to JVM shutdown to free resources on all managed beans
*/
static {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
if (!isFinalizeBeansDelegated) {
finalizeManagedBeans();
}
}
});
}
/**
* Singleton constructor
*/
private BeanFactory() {
// Singleton pattern
}
/**
* @return the default factory
*/
public static BeanFactory getFactory() {
return BeanFactory.getFactory(DefaultApplicationContext.class);
}
/**
* @param context
* @return factory from the specified scope
*/
public static BeanFactory getFactory(Class<? extends DefaultApplicationContext> context) {
ClassLoader currentClassLoader = BeanFactory.class.getClassLoader();
if (!factoryInstancesPerClassloader.containsKey(currentClassLoader)) {
Map<Class<? extends DefaultApplicationContext>, BeanFactory> factoryInstances = new HashMap<Class<? extends DefaultApplicationContext>, ManiocFramework.BeanFactory>();
factoryInstancesPerClassloader.put(currentClassLoader, factoryInstances);
}
Map<Class<? extends DefaultApplicationContext>, BeanFactory> anInstancesGroup = factoryInstancesPerClassloader.get(currentClassLoader);
if (!anInstancesGroup.containsKey(context)) {
BeanFactory newInstance = new BeanFactory();
newInstance.context = context;
anInstancesGroup.put(context, newInstance);
}
return anInstancesGroup.get(context);
}
private static Map<Object, Class<? extends DefaultApplicationContext>> getManagedBeansForCurrentClassLoader() {
ClassLoader currentClassLoader = BeanFactory.class.getClassLoader();
if (!managedBeansPerClassloader.containsKey(currentClassLoader)) {
Map<Object, Class<? extends DefaultApplicationContext>> newMap = new HashMap<Object, Class<? extends DefaultApplicationContext>>();
managedBeansPerClassloader.put(currentClassLoader, newMap);
}
return managedBeansPerClassloader.get(currentClassLoader);
}
private static Map<Class<? extends DefaultApplicationContext>, BeanFactory> getFactoryInstancesForCurrentClassLoader() {
ClassLoader currentClassLoader = BeanFactory.class.getClassLoader();
if (!factoryInstancesPerClassloader.containsKey(currentClassLoader)) {
Map<Class<? extends DefaultApplicationContext>, BeanFactory> factoryInstances = new HashMap<Class<? extends DefaultApplicationContext>, ManiocFramework.BeanFactory>();
factoryInstancesPerClassloader.put(currentClassLoader, factoryInstances);
}
return factoryInstancesPerClassloader.get(currentClassLoader);
}
/**
* Checks if the current factory has already worked whith a bean of the
* specified type
*
* @param <T>
* @param classType
* @return true if found
*/
public <T> boolean contains(Class<T> classType) {
Set<Object> beanSet = getManagedBeansForCurrentClassLoader().keySet();
for (Object aBean : beanSet) {
Class<? extends DefaultApplicationContext> beanContext = getManagedBeansForCurrentClassLoader().get(aBean);
if (!beanContext.equals(this.context)) {
continue;
}
if (classType.isInstance(aBean)) {
return true;
}
}
return false;
}
/**
* Returns the bean of the specified type
*
* @param <T>
* @param classType
* @return
*/
public <T> T getBean(Class<T> classType) {
boolean isRegisteredManually = isRegisteredManually(classType);
checkVisibilityFromCurrentContext(classType);
T bean = getAlreadyExistingBean(classType);
if (isRegisteredManually) {
if (bean == null) {
throw new ManiocException("Unable to find a bean of type " + classType.getName() + " which is annotated with @" + ManagedBean.class.getSimpleName() + "(registeredManually=true). Application context is " + this.context.getSimpleName());
}
}
if (bean != null) {
return bean;
}
if (!classType.isInterface()) {
return createBean(classType);
}
return null;
}
/**
* Returns the creation context of the given bean or null if no bean
* found
*
* @param bean
* @return the context class if bean found
*/
public static Class<? extends DefaultApplicationContext> getBeanContext(Object bean) {
Class<? extends DefaultApplicationContext> beanContext = getManagedBeansForCurrentClassLoader().get(bean);
return beanContext;
}
/**
* Checks if this kind of class is reachable from the context of the
* factory
*
* @param <T>
* @param classType
*/
private <T> void checkVisibilityFromCurrentContext(Class<T> classType) {
ManagedBean annotation = classType.getAnnotation(ManagedBean.class);
if (annotation == null) {
return;
}
Class<? extends DefaultApplicationContext> reachableContext = annotation.applicationContext();
boolean isClassContextIsChildOfCurrent = reachableContext.isAssignableFrom(this.context);
if (!isClassContextIsChildOfCurrent) {
throw new ManiocException("Cannot get a bean type " + classType.getName() + " from context " + this.context.getSimpleName() + " because it has been created in " + reachableContext.getSimpleName() + " which is not a child of "
+ this.context.getSimpleName());
}
}
/**
* Checks if the factory should contain only one instance of this class. <br/>
* There's two cases : \@ManagerBean.scope() is set to 'singleton' or
* \@ManagedBean.registerManually() is set to true
*
* @param <T>
* @param classType
* @return true if singleton
*/
private <T> boolean isSingleton(Class<T> classType) {
ManagedBean annotation = classType.getAnnotation(ManagedBean.class);
if (annotation == null) {
return false;
}
BeanScope beanScope = annotation.scope();
if (beanScope.equals(BeanScope.SINGLETON)) {
return true;
}
if (annotation.registeredManually()) {
return true;
}
return false;
}
/**
* Checks if this kind of bean should be or not created by the factory
*
* @param <T>
* @param classType
* @return true if the class is annotated with
* \@ManagedBean(registeredManually=true)
*/
private <T> boolean isRegisteredManually(Class<T> classType) {
ManagedBean annotation = classType.getAnnotation(ManagedBean.class);
if (annotation == null) {
return false;
}
return annotation.registeredManually();
}
/**
* Looks for an existing bean inside all the contexts hierarchy
*
* @param <T>
* @param classType
* @return
*/
private <T> T getAlreadyExistingBean(Class<T> classType) {
if (singletonsMap.containsKey(classType)) {
Object object = singletonsMap.get(classType);
Class<? extends Object> implementationType = object.getClass();
ManagedBean annotation = implementationType.getAnnotation(ManagedBean.class);
if (annotation == null) {
throw new ManiocException("Error on " + implementationType.getName() + " . All the class instances you want to inject must have the annotation @" + ManagedBean.class.getSimpleName() + " declared. Application context is "
+ this.context.getSimpleName());
}
return (T) object;
}
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null) {
return parentBeanFactory.getAlreadyExistingBean(classType);
}
return null;
}
/**
* @return the parent bean factory or null if this context has no parent
*/
private BeanFactory getParentBeanFactory() {
Class<? extends DefaultApplicationContext> parentContext = getParentContext();
if (parentContext == null) {
return null;
}
return getFactoryInstancesForCurrentClassLoader().get(parentContext);
}
/**
* Looks for the parent context of this context if it exists
*
* @param childContext
* @return the parent context or null if there's no parent
*/
private Class<? extends DefaultApplicationContext> getParentContext() {
Class<?> aSuperClass = this.context.getSuperclass();
if (aSuperClass != null && aSuperClass.isAssignableFrom(DefaultApplicationContext.class)) {
return (Class<? extends DefaultApplicationContext>) aSuperClass;
}
return null;
}
/**
* Determines all the parent contexts that the given context can see
*
* @param childContext
* @return a list of contexts from the child to the oldest parent
*/
private List<Class<? extends DefaultApplicationContext>> getContextHierarchy(Class<? extends DefaultApplicationContext> childContext) {
List<Class<? extends DefaultApplicationContext>> result = new ArrayList<Class<? extends DefaultApplicationContext>>();
List<Class<? extends DefaultApplicationContext>> fifo = new ArrayList<Class<? extends DefaultApplicationContext>>();
fifo.add(childContext);
while (!fifo.isEmpty()) {
Class<? extends DefaultApplicationContext> aClass = fifo.remove(0);
Class<?> aSuperClass = aClass.getSuperclass();
if (aSuperClass != null && aSuperClass.isAssignableFrom(DefaultApplicationContext.class)) {
fifo.add((Class<? extends DefaultApplicationContext>) aSuperClass);
}
result.add(aClass);
}
return result;
}
/**
* Register the given bean in the current BeanFactory. If this bean's
* fields are annotated with @InjectedBean, they would be injected too.<br/>
*
* @param classType
* (prefer using the interface here)
* @param aBean
*/
public <T> void register(Class<T> classType, T bean) {
boolean isInterface = classType.isInterface();
if (!isInterface) {
checkIfBeanIsManageable(classType);
boolean isSingleton = isSingleton(classType);
if (isSingleton) {
if (singletonsMap.containsKey(classType)) {
throw new ManiocException("Duplicate beans of type " + classType.getName() + " in the context " + this.context.getSimpleName());
}
singletonsMap.put(classType, bean);
}
}
if (isInterface) {
checkIfBeanIsManageable(bean.getClass());
boolean isSingleton = isSingleton(bean.getClass());
if (isSingleton) {
if (singletonsMap.containsKey(classType)) {
throw new ManiocException("Duplicate beans of type " + classType.getName() + " in the context " + this.context.getSimpleName());
}
if (singletonsMap.containsKey(bean.getClass())) {
throw new ManiocException("Duplicate beans of type " + bean.getClass().getName() + " in the context " + this.context.getSimpleName());
}
singletonsMap.put(classType, bean);
singletonsMap.put(bean.getClass(), bean);
}
}
BeanInjector beanInjector = BeanInjector.getInjector();
try {
beanInjector.inject(bean);
} catch (ManiocException re) {
singletonsMap.remove(classType);
throw new ManiocException("Error while registering a bean of type " + classType.getName() + " . Application context is " + this.context.getSimpleName(), re);
}
getManagedBeansForCurrentClassLoader().put(bean, this.context);
}
/**
* Creates a new class instance (on the desired context if specified)
*
* @param <T>
* @param classType
* @return the newly created bean
*/
private <T> T createBean(Class<T> classType) {
checkIfBeanIsManageable(classType);
boolean isRegisteredManually = isRegisteredManually(classType);
if (isRegisteredManually) {
throw new ManiocException("Cannot create a bean of type " + classType.getName() + " because it has the annotation @" + ManagedBean.class.getSimpleName() + ".registeredManually() set to true. Current context is "
+ this.context.getSimpleName());
}
ManagedBean annotation = classType.getAnnotation(ManagedBean.class);
Class<? extends DefaultApplicationContext> classTypeContext = annotation.applicationContext();
BeanFactory classTypeFactory = BeanFactory.getFactory(classTypeContext);
T newInstance = classTypeFactory.createBeanFromAnnotatedConstructor(classType);
if (newInstance == null) {
newInstance = classTypeFactory.createBeanFromAnnotatedFactoryMethod(classType);
}
if (newInstance == null) {
newInstance = classTypeFactory.createBeanFromDefaultConstructor(classType);
}
if (newInstance != null) {
classTypeFactory.register(classType, newInstance);
classTypeFactory.postConstruct(classType, newInstance);
return newInstance;
}
throw new ManiocException("BeanFactory unexpected error. Failed to create bean of type " + classType.getName());
}
/**
* @param <T>
* @param classType
* @return newly created bean
*/
private <T> T createBeanFromDefaultConstructor(Class<T> classType) {
Constructor<T> defaultConstructor = getDefaultConstructor(classType);
if (defaultConstructor != null) {
try {
T newInstance = classType.newInstance();
return newInstance;
} catch (Exception e) {
throw new ManiocException("BeanFactory failed to create bean of type " + classType.getName() + " from its default constructor. Application context is " + this.context.getSimpleName(), e);
}
}
return null;
}
/**
* @param <T>
* @param classType
* @return newly created bean
*/
private <T> T createBeanFromAnnotatedFactoryMethod(Class<T> classType) {
Method annotatedFactoryMethod = getAnnotatedFactoryMethod(classType);
if (annotatedFactoryMethod != null) {
Class<?>[] parameterTypes = annotatedFactoryMethod.getParameterTypes();
Object[] parameterBeans = BeanFactory.getBeans(parameterTypes, this.context);
try {
T newInstance = (T) annotatedFactoryMethod.invoke(null, parameterBeans);
return newInstance;
} catch (Exception e) {
throw new ManiocException("BeanFactory failed to create bean of type " + classType.getName() + " from its method " + annotatedFactoryMethod.getName() + ". Application context is " + this.context.getSimpleName(), e);
}
}
return null;
}
/**
* @param <T>
* @param classType
* @return a nealy created bean
*/
private <T> T createBeanFromAnnotatedConstructor(Class<T> classType) {
Constructor<T> annotatedConstructor = getAnnotatedConstructor(classType);
if (annotatedConstructor != null) {
Class<?>[] parameterTypes = annotatedConstructor.getParameterTypes();
Object[] parameterBeans = BeanFactory.getBeans(parameterTypes, this.context);
try {
T newInstance = annotatedConstructor.newInstance(parameterBeans);
return newInstance;
} catch (Exception e) {
throw new ManiocException("BeanFactory failed to create bean of type " + classType.getName() + " from its annotated constructor . Application context is " + this.context.getSimpleName(), e);
}
}
return null;
}
/**
* Calls all methods annotated with \@PostConstruct. Injects parameters
* bean when it's possible (or inject a null value).<br/>
* This is used to initialize beans after construction.<br/>
*
* @param <T>
* @param beanType
* @param bean
* which need to be initialized by calling some post
* construction methods
*/
private <T> void postConstruct(Class<T> beanType, Object bean) {
Method[] methods = beanType.getMethods();
for (Method aMethod : methods) {
PostConstruct annotation = aMethod.getAnnotation(PostConstruct.class);
if (annotation == null) {
continue;
}
Class<?>[] parameterTypes = aMethod.getParameterTypes();
Object[] parameterBeans = BeanFactory.getBeans(parameterTypes, this.context);
try {
aMethod.invoke(bean, parameterBeans);
} catch (Exception e) {
throw new ManiocException("BeanFactory failed to invoke the post construction method '" + aMethod.getName() + "' on the bean of type " + beanType.getName() + ". Application context is " + this.context.getSimpleName(), e);
}
}
}
/**
* Calls all methods annotated with \@PreDetroy. Injects parameters bean
* when it's possible (or inject a null value).<br/>
* This is used to free resources on beans on JVM shutdown.<br/>
*
* @param bean
* @param context
*/
private static void preDestroy(Object bean, Class<? extends DefaultApplicationContext> context) {
Class<? extends Object> beanType = bean.getClass();
Method[] methods = beanType.getMethods();
for (Method aMethod : methods) {
PreDestroy annotation = aMethod.getAnnotation(PreDestroy.class);
if (annotation == null) {
continue;
}
Class<?>[] parameterTypes = aMethod.getParameterTypes();
Object[] parameterBeans = BeanFactory.getBeans(parameterTypes, context);
try {
aMethod.invoke(bean, parameterBeans);
} catch (Exception e) {
throw new ManiocException("BeanFactory failed to invoke the pre detroy method '" + aMethod.getName() + "' on the bean of type " + beanType.getName() + ". Application context is " + context.getName(), e);
}
}
}
/**
* Takes an array of communication + a context and return an array containing
* beans from this context. <br/>
* Useful to get beans to inject in a constructor or a method. <br/>
*
* @param classTypes
* @param context
* @return an array of beans. Could contain null value if no bean is
* found.
*/
private static Object[] getBeans(Class<?>[] classTypes, Class<? extends DefaultApplicationContext> context) {
BeanFactory contextFactory = getFactoryInstancesForCurrentClassLoader().get(context);
Object[] beans = new Object[classTypes.length];
for (int i = 0; i < classTypes.length; i++) {
Class<?> aClassType = classTypes[i];
Object beanToInject = contextFactory.getBean(aClassType);
beans[i] = beanToInject;
}
return beans;
}
/**
* Indicates if Manioc has to finalize or not managed beans on JVM shutdown. Set this to true if you're in a web app, implements ServletContextListener and call finalizeManagedBeans() directly.
*/
public static void delegatesBeanFinalization(boolean isDelegated) {
isFinalizeBeansDelegated = isDelegated;
}
/**
* Finalizes all managed beans by calling methods annotated with \@PreDestroy annotation
*/
public static void finalizeManagedBeans() {
for (Map<Object, Class<? extends DefaultApplicationContext>> beansForAClassLoader : managedBeansPerClassloader.values()) {
Set<Object> beanSet = beansForAClassLoader.keySet();
for (Object aBean : beanSet) {
Class<? extends DefaultApplicationContext> beanContext = beansForAClassLoader.get(aBean);
BeanFactory.preDestroy(aBean, beanContext);
}
}
}
/**
* Verifies if this class is annotated with \@ManagedBean <br/>
* which means that this factory can handle it
*
* @param <T>
* @param classType
* @throws ManiocException
* if the class is not annotated with \@ManagedBean
*/
private <T> void checkIfBeanIsManageable(Class<T> classType) throws ManiocException {
ManagedBean annotation = classType.getAnnotation(ManagedBean.class);
if (annotation == null) {
throw new ManiocException("Error on " + classType + " . All the class instances you want to inject must have the annotation @" + ManagedBean.class.getSimpleName() + " declared. Application context is " + this.context.getSimpleName());
}
}
/**
* @param <T>
* @param classType
* @return the constructor annotated with \@Constructor or null if no
* one is found
*/
private <T> Constructor<T> getAnnotatedConstructor(Class<T> classType) {
Constructor<T>[] constructors = (Constructor<T>[]) classType.getConstructors();
List<Constructor<T>> result = new ArrayList<Constructor<T>>();
for (Constructor<T> aConstructor : constructors) {
int modifiers = aConstructor.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0) {
continue;
}
Construct annotation = aConstructor.getAnnotation(Construct.class);
if (annotation == null) {
continue;
}
result.add(aConstructor);
}
if (result.size() == 1) {
return result.get(0);
}
if (result.size() > 1) {
throw new ManiocException("BeanFactory cannot determine which constructor to use on " + classType.getName() + " because there's more than one constructor annotated with @" + Construct.class.getSimpleName());
}
return null;
}
/**
* @param <T>
* @param classType
* @return the method annotated with \@Constructor or null if no one is
* found
*/
private <T> Method getAnnotatedFactoryMethod(Class<T> classType) {
Method[] methods = classType.getMethods();
List<Method> result = new ArrayList<Method>();
for (Method aMethod : methods) {
Class<?> returnType = aMethod.getReturnType();
if (!returnType.equals(classType)) {
continue;
}
int modifiers = aMethod.getModifiers();
if ((modifiers & Modifier.PUBLIC & Modifier.STATIC) != 0) {
continue;
}
Construct annotation = aMethod.getAnnotation(Construct.class);
if (annotation == null) {
continue;
}
result.add(aMethod);
}
if (result.size() == 1) {
return result.get(0);
}
if (result.size() > 1) {
throw new ManiocException("BeanFactory cannot determine which method to use to construct a bean on " + classType.getName() + " because there's more than one method annotated with @" + Construct.class.getSimpleName());
}
return null;
}
/**
* @param classType
* @return the constructor with no parameter or null if no one if found
*/
private <T> Constructor<T> getDefaultConstructor(Class<T> classType) {
Constructor<T>[] constructors = (Constructor<T>[]) classType.getConstructors();
for (Constructor<T> aConstructor : constructors) {
Class<?>[] parameterTypes = aConstructor.getParameterTypes();
if (parameterTypes.length == 0) {
return aConstructor;
}
}
return null;
}
}
/**
* Allows to isolate beans on different scopes. All scopes need to
* inheritate from this interface. A child scope can see beans from its
* parents.
*
* @author Alexandre de Pellegrin
*
*/
public static class DefaultApplicationContext {
}
/**
* Annotation used to mark methods to call to instance a class
*
* @author Alexandre de Pellegrin
*
*/
@Target({ METHOD, CONSTRUCTOR })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Construct {
}
/**
* Annotation used to inject beans managed by ManiocFactory
*
* @author Alexandre de Pellegrin
*
*/
@Target({ FIELD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface InjectedBean {
/**
* @return the desired implementation. Optional. If not precised, it
* takes the field's type
*/
public Class<?> implementation() default Object.class;
}
/**
* Annotation used to specify a default implementation on an interface
*
* @author Alexandre de Pellegrin
*
*/
@Target({ TYPE })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ImplementedBy {
/**
* @return the desired implementation. Optional. If not precised, it
* takes the field's type
*/
public Class<?> value();
}
/**
* Annotation used to auto create beans with the BeanFactory
*
* @author Alexandre de Pellegrin
*
*/
@Target({ TYPE })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface ManagedBean {
/**
* @return true if the bean instance is automatically created by the
* BeanFactory or registered manually
*/
public boolean registeredManually() default false;
/**
* @return the context of the bean (which is linked to its visibility
* with other contexts)
*/
public Class<? extends DefaultApplicationContext> applicationContext() default DefaultApplicationContext.class;
/**
* @return the scope of the bean : one instance per context (singleton)
* or construction of a new instance on each request to the bean
* factory <br/>
*/
public BeanScope scope() default BeanScope.SINGLETON;
}
/**
* Scope of a bean. Indicates if a bean is a singleton on its context or <br/>
* if a new instance should be constructed on each request to the factory. <br/>
*
* @author Alexandre de Pellegrin
*
*/
public enum BeanScope {
SINGLETON, PROTOTYPE
}
/**
* Annotation used to mark methods to call on JVM shutdown
*
* @author Alexandre de Pellegrin
*
*/
@Target({ METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface PostConstruct {
}
/**
* Annotation used to mark methods to call on JVM shutdown
*
* @author Alexandre de Pellegrin
*
*/
@Target({ METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface PreDestroy {
}
/**
* Just an unchecked exception you can trap if you want
*
* @author Alexandre de Pellegrin
*
*/
public static class ManiocException extends RuntimeException {
public ManiocException(String msg, Throwable t) {
super(msg, t);
}
public ManiocException(String msg) {
super(msg);
}
}
}