package com.firefly.core;
import com.firefly.annotation.Component;
import com.firefly.annotation.Inject;
import com.firefly.annotation.Proxies;
import com.firefly.annotation.Proxy;
import com.firefly.core.support.BeanDefinition;
import com.firefly.core.support.annotation.AnnotationBeanDefinition;
import com.firefly.core.support.annotation.AnnotationBeanReader;
import com.firefly.core.support.xml.*;
import com.firefly.utils.ConvertUtils;
import com.firefly.utils.ReflectUtils;
import com.firefly.utils.StringUtils;
import com.firefly.utils.VerifyUtils;
import com.firefly.utils.classproxy.ClassProxy;
import com.firefly.utils.classproxy.JavassistClassProxyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
* The core application context mixed XML and annotation bean management
*/
public class XmlApplicationContext extends AbstractApplicationContext {
private static Logger log = LoggerFactory.getLogger("firefly-system");
public XmlApplicationContext() {
this(null);
}
public XmlApplicationContext(String file) {
super(file);
}
@Override
protected List<BeanDefinition> getBeanDefinitions(String file) {
List<BeanDefinition> annotationBeanDefs = new AnnotationBeanReader(file).loadBeanDefinitions();
List<BeanDefinition> xmlBeanDefs = new XmlBeanReader(file).loadBeanDefinitions();
if (annotationBeanDefs != null && xmlBeanDefs != null) {
log.debug("mixed bean");
annotationBeanDefs.addAll(xmlBeanDefs);
return annotationBeanDefs;
} else if (annotationBeanDefs != null) {
log.debug("annotation bean");
return annotationBeanDefs;
} else if (xmlBeanDefs != null) {
log.debug("xml bean");
return xmlBeanDefs;
}
return null;
}
@Override
protected Object inject(BeanDefinition beanDef) {
if (beanDef instanceof XmlBeanDefinition)
return xmlInject(beanDef);
else if (beanDef instanceof AnnotationBeanDefinition)
return annotationInject(beanDef);
else
return null;
}
private Object getInstance(BeanDefinition beanDef) {
if (StringUtils.hasText(beanDef.getId())) {
return map.get(beanDef.getId());
} else {
Object instance = map.get(beanDef.getClassName());
if (instance != null) {
return instance;
}
String[] keys = beanDef.getInterfaceNames();
for (String k : keys) {
instance = map.get(k);
if (instance != null) {
return instance;
}
}
return null;
}
}
private Object xmlInject(BeanDefinition beanDef) {
Object instance = getInstance(beanDef);
if (instance == null) {
final XmlBeanDefinition beanDefinition = (XmlBeanDefinition) beanDef;
Class<?> clazz = null;
try {
clazz = XmlApplicationContext.class.getClassLoader().loadClass(beanDefinition.getClassName());
if (beanDefinition.getContructorParameters().size() <= 0) {
instance = clazz.newInstance();
} else {
List<Object> constructorParameters = new ArrayList<>();
for (int i = 0; i < beanDefinition.getContructorParameters().size(); i++) {
Object p = getInjectArg(beanDefinition.getContructorParameters().get(i), beanDefinition.getConstructor().getParameterTypes()[i]);
constructorParameters.add(p);
}
instance = beanDefinition.getConstructor().newInstance(constructorParameters.toArray());
}
instance = createProxy(clazz, instance);
} catch (Throwable t) {
log.error("object initiate error", t);
}
if (instance != null) {
final Object obj = instance;
ReflectUtils.getSetterMethods(clazz, (propertyName, method) -> {
XmlManagedNode value = beanDefinition.getProperties().get(propertyName);
if (value != null) {
try {
method.invoke(obj, getInjectArg(value, method.getParameterTypes()[0]));
} catch (Throwable t) {
log.error("xml inject error", t);
}
}
return false;
});
} else {
error("initialize XML bean exception, the instance is null");
}
fieldInject(beanDefinition, instance);
methodInject(beanDefinition, instance);
addObjectToContext(beanDefinition, instance);
return instance;
} else {
return instance;
}
}
private Object createProxy(Class<?> clazz, Object srcObject) throws Throwable {
Object instance = srcObject;
List<Proxy> proxies = new ArrayList<>();
for (Annotation annotation : clazz.getAnnotations()) {
if (annotation.annotationType().equals(Proxy.class)) {
proxies.add((Proxy) annotation);
} else if (annotation.annotationType().equals(Proxies.class)) {
proxies.addAll(Arrays.asList(((Proxies) annotation).value()));
} else {
Proxy[] p = annotation.annotationType().getAnnotationsByType(Proxy.class);
if (p != null && p.length > 0) {
proxies.addAll(Arrays.asList(annotation.annotationType().getAnnotationsByType(Proxy.class)));
}
}
}
if (!proxies.isEmpty()) {
for (Proxy p : proxies) {
if (!Arrays.asList(p.proxyClass().getInterfaces()).contains(ClassProxy.class)) {
continue;
}
String key;
if (p.proxyClass().getAnnotation(Component.class) != null) {
String id = p.proxyClass().getAnnotation(Component.class).value();
if (StringUtils.hasText(id)) {
key = id;
} else {
key = p.proxyClass().getName();
}
} else {
key = p.proxyClass().getName();
}
BeanDefinition b = findBeanDefinition(key);
if (b != null) {
instance = JavassistClassProxyFactory.INSTANCE.createProxy(instance, (ClassProxy) inject(b), null);
}
}
}
return instance;
}
@SuppressWarnings("unchecked")
private Object getInjectArg(XmlManagedNode value, Class<?> parameterType) {
if (value instanceof ManagedValue) {
ManagedValue managedValue = (ManagedValue) value;
String typeName;
if (parameterType == null) {
typeName = VerifyUtils.isEmpty(managedValue.getTypeName()) ? null
: managedValue.getTypeName();
} else {
typeName = VerifyUtils.isEmpty(managedValue.getTypeName()) ? parameterType.getName() : managedValue.getTypeName();
}
log.debug("value type [{}]", typeName);
return getValueArg(managedValue, typeName);
} else if (value instanceof ManagedRef) {
return getRefArg((ManagedRef) value);
} else if (value instanceof ManagedList) {
return getListArg((ManagedList<XmlManagedNode>) value, parameterType);
} else if (value instanceof ManagedArray) {
return getArrayArg((ManagedArray<XmlManagedNode>) value, parameterType);
} else if (value instanceof ManagedMap) {
return getMapArg((ManagedMap<XmlManagedNode, XmlManagedNode>) value, parameterType);
} else
return null;
}
private Object getValueArg(ManagedValue managedValue, String typeName) {
return ConvertUtils.convert(managedValue.getValue(), typeName);
}
private Object getRefArg(ManagedRef ref) {
Object instance = map.get(ref.getBeanName());
if (instance == null) {
BeanDefinition b = findBeanDefinition(ref.getBeanName());
if (b != null)
instance = inject(b);
}
return instance;
}
@SuppressWarnings("unchecked")
private Object getListArg(ManagedList<XmlManagedNode> values, Class<?> setterParamType) {
Collection<Object> collection = null;
if (VerifyUtils.isNotEmpty(values.getTypeName())) {
try {
collection = (Collection<Object>) XmlApplicationContext.class
.getClassLoader()
.loadClass(values.getTypeName())
.newInstance();
} catch (Throwable t) {
log.error("list inject error", t);
}
} else {
collection = (setterParamType == null ? new ArrayList<>()
: ConvertUtils.getCollectionObj(setterParamType));
}
if (collection != null) {
for (XmlManagedNode item : values) {
Object listValue = getInjectArg(item, null);
collection.add(listValue);
}
}
return collection;
}
private Object getArrayArg(ManagedArray<XmlManagedNode> values, Class<?> setterParamType) {
Collection<Object> collection = new ArrayList<>();
for (XmlManagedNode item : values) {
Object listValue = getInjectArg(item, null);
collection.add(listValue);
}
return ConvertUtils.convert(collection, setterParamType);
}
@SuppressWarnings("unchecked")
private Object getMapArg(ManagedMap<XmlManagedNode, XmlManagedNode> values, Class<?> setterParamType) {
Map<Object, Object> m = null;
if (VerifyUtils.isNotEmpty(values.getTypeName())) {
try {
m = (Map<Object, Object>) XmlApplicationContext.class.getClassLoader()
.loadClass(values.getTypeName())
.newInstance();
} catch (Throwable t) {
log.error("map inject error", t);
}
} else {
m = (setterParamType == null ? new HashMap<>() : ConvertUtils.getMapObj(setterParamType));
if (m != null && log.isDebugEnabled()) {
log.debug("map ret [{}]", m.getClass().getName());
}
}
if (m != null) {
for (XmlManagedNode o : values.keySet()) {
Object k = getInjectArg(o, null);
Object v = getInjectArg(values.get(o), null);
m.put(k, v);
}
}
return m;
}
private Object annotationInject(BeanDefinition beanDef) {
Object instance = getInstance(beanDef);
if (instance == null) {
AnnotationBeanDefinition beanDefinition = (AnnotationBeanDefinition) beanDef;
// constructor injecting
instance = constructorInject(beanDefinition);
try {
instance = createProxy(instance.getClass(), instance);
} catch (Throwable t) {
log.error("create proxy exception", t);
}
fieldInject(beanDefinition, instance);
methodInject(beanDefinition, instance);
addObjectToContext(beanDefinition, instance);
return instance;
} else {
return instance;
}
}
private Object constructorInject(AnnotationBeanDefinition beanDefinition) {
Class<?>[] params = beanDefinition.getConstructor().getParameterTypes();
Object[] p = new Object[params.length];
injectObject(params, p);
Object instance = null;
try {
instance = beanDefinition.getConstructor().newInstance(p);
} catch (Throwable t) {
log.error("constructor injecting error", t);
}
return instance;
}
private void injectObject(Class<?>[] params, Object[] p) {
for (int i = 0; i < p.length; i++) {
String key = params[i].getName();
Object instance = map.get(key);
if (instance != null) {
p[i] = instance;
} else {
BeanDefinition b = findBeanDefinition(key);
if (b != null)
p[i] = inject(b);
}
}
}
private void fieldInject(AnnotationBeanDefinition beanDefinition, final Object object) {
for (Field field : beanDefinition.getInjectFields()) {
field.setAccessible(true);
Class<?> clazz = field.getType();
String id = field.getAnnotation(Inject.class).value();
String key = VerifyUtils.isNotEmpty(id) ? id : clazz.getName();
Object instance = map.get(key);
if (instance == null) {
BeanDefinition b = findBeanDefinition(key);
if (b != null)
instance = inject(b);
}
if (instance != null) {
try {
field.set(object, instance);
} catch (Throwable t) {
log.error("field injecting error", t);
}
}
}
}
private void methodInject(AnnotationBeanDefinition beanDefinition, final Object object) {
for (Method method : beanDefinition.getInjectMethods()) {
method.setAccessible(true);
Class<?>[] params = method.getParameterTypes();
Object[] p = new Object[params.length];
injectObject(params, p);
try {
method.invoke(object, p);
} catch (Throwable t) {
log.error("method injecting error", t);
}
}
}
}