/**
* Dianping.com Inc.
* Copyright (c) 2003-2013 All Rights Reserved.
*/
package com.dianping.pigeon.remoting.common.config;
import com.dianping.pigeon.config.ConfigManagerLoader;
import com.dianping.pigeon.log.Logger;
import com.dianping.pigeon.log.LoggerLoader;
import com.dianping.pigeon.remoting.ServiceFactory;
import com.dianping.pigeon.remoting.common.util.Constants;
import com.dianping.pigeon.remoting.common.util.ServiceConfigUtils;
import com.dianping.pigeon.remoting.invoker.concurrent.InvocationCallback;
import com.dianping.pigeon.remoting.invoker.config.InvokerConfig;
import com.dianping.pigeon.remoting.invoker.config.annotation.Reference;
import com.dianping.pigeon.remoting.provider.config.ProviderConfig;
import com.dianping.pigeon.remoting.provider.config.ServerConfig;
import com.dianping.pigeon.remoting.provider.config.annotation.Service;
import com.dianping.pigeon.remoting.provider.config.spring.ServiceInitializeListener;
import com.dianping.pigeon.util.ClassUtils;
import com.dianping.pigeon.util.LangUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class AnnotationBean extends ServiceInitializeListener implements DisposableBean,
BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware {
private static final Logger logger = LoggerLoader.getLogger(AnnotationBean.class);
private String annotationPackage = ConfigManagerLoader.getConfigManager().getStringValue(
"pigeon.provider.interface.packages", "com.dianping");
private String[] annotationPackages = new String[] { "com.dianping" };
private final ConcurrentMap<String, InvokerConfig<?>> invokerConfigs = new ConcurrentHashMap<String, InvokerConfig<?>>();
public String getPackage() {
return annotationPackage;
}
public void setPackage(String annotationPackage) {
this.annotationPackage = annotationPackage;
this.annotationPackages = (annotationPackage == null || annotationPackage.length() == 0) ? null
: Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
}
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (annotationPackage == null || annotationPackage.length() == 0) {
return;
}
if (beanFactory instanceof BeanDefinitionRegistry) {
try {
// init scanner
Class<?> scannerClass = ClassUtils
.loadClass("org.springframework.context.annotation.ClassPathBeanDefinitionScanner");
Object scanner = scannerClass.getConstructor(
new Class<?>[] { BeanDefinitionRegistry.class, boolean.class }).newInstance(
new Object[] { (BeanDefinitionRegistry) beanFactory, true });
// add filter
Class<?> filterClass = ClassUtils
.loadClass("org.springframework.core.type.filter.AnnotationTypeFilter");
Object filter = filterClass.getConstructor(Class.class).newInstance(Service.class);
Method addIncludeFilter = scannerClass.getMethod("addIncludeFilter",
ClassUtils.loadClass("org.springframework.core.type.filter.TypeFilter"));
addIncludeFilter.invoke(scanner, filter);
// scan packages
String[] packages = Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
Method scan = scannerClass.getMethod("scan", new Class<?>[] { String[].class });
scan.invoke(scanner, new Object[] { packages });
} catch (Throwable e) {
// spring 2.0
}
}
}
public int getDefaultPort(int port) {
if (port == 4040) {
try {
String app = ConfigManagerLoader.getConfigManager().getAppName();
if (StringUtils.isNotBlank(app)) {
return LangUtils.hash(app, 6000, 2000);
}
} catch (Throwable t) {
}
}
return port;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = AopUtils.getTargetClass(bean);
if (beanClass == null || !isMatchPackage(beanClass.getName())) {
return bean;
}
Service service = beanClass.getAnnotation(Service.class);
if (service != null) {
Class serviceInterface = service.interfaceClass();
if (void.class.equals(service.interfaceClass())) {
serviceInterface = ServiceConfigUtils.getServiceInterface(beanClass);
}
if (serviceInterface == null) {
serviceInterface = beanClass;
}
ProviderConfig<Object> providerConfig = new ProviderConfig<Object>(serviceInterface, bean);
providerConfig.setService(bean);
providerConfig.setUrl(service.url());
providerConfig.setVersion(service.version());
providerConfig.setSharedPool(service.useSharedPool());
providerConfig.setActives(service.actives());
ServerConfig serverConfig = new ServerConfig();
serverConfig.setPort(getDefaultPort(service.port()));
serverConfig.setSuffix(service.group());
serverConfig.setAutoSelectPort(service.autoSelectPort());
providerConfig.setServerConfig(serverConfig);
ServiceFactory.addService(providerConfig);
}
postProcessBeforeInitialization(bean, beanName);
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!isMatchPackage(bean.getClass().getName())) {
return bean;
}
Method[] methods = bean.getClass().getMethods();
for (Method method : methods) {
String name = method.getName();
if (name.length() > 3 && name.startsWith("set") && method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers())) {
try {
Reference reference = method.getAnnotation(Reference.class);
if (reference != null) {
Object value = refer(reference, method.getParameterTypes()[0]);
if (value != null) {
method.invoke(bean, new Object[] {});
}
}
} catch (Throwable e) {
logger.error("Failed to init remote service reference at method " + name + " in class "
+ bean.getClass().getName() + ", cause: " + e.getMessage(), e);
}
}
}
Class<?> superClass = bean.getClass().getSuperclass();
while (superClass != null && isMatchPackage(superClass)) {
referFields(bean, superClass.getDeclaredFields());
superClass = superClass.getSuperclass();
}
referFields(bean, bean.getClass().getDeclaredFields());
return bean;
}
private void referFields(Object bean, Field[] fields) {
for (Field field : fields) {
try {
if (!field.isAccessible()) {
field.setAccessible(true);
}
Reference reference = field.getAnnotation(Reference.class);
if (reference != null) {
Object value = refer(reference, field.getType());
if (value != null) {
field.set(bean, value);
}
}
} catch (Throwable e) {
logger.error("Failed to init remote service reference at field " + field.getName() + " in class "
+ bean.getClass().getName() + ", cause: " + e.getMessage(), e);
}
}
}
private Object refer(Reference reference, Class<?> referenceClass) { // method.getParameterTypes()[0]
String interfaceName;
if (!void.class.equals(reference.interfaceClass())) {
interfaceName = reference.interfaceClass().getName();
} else if (referenceClass.isInterface()) {
interfaceName = referenceClass.getName();
} else {
throw new IllegalStateException(
"The @Reference undefined interfaceClass or interfaceName, and the property type "
+ referenceClass.getName() + " is not a interface.");
}
String callbackClassName = reference.callback();
InvocationCallback callback = null;
if (StringUtils.isNotBlank(callbackClassName)) {
Class<?> clazz;
try {
clazz = ClassUtils.loadClass(callbackClassName);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("The @Reference undefined callback " + callbackClassName
+ ", is not a ServiceCallback interface.");
}
if (!InvocationCallback.class.isAssignableFrom(clazz)) {
throw new IllegalStateException("The @Reference undefined callback " + callbackClassName
+ ", is not a ServiceCallback interface.");
}
try {
callback = (InvocationCallback) clazz.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("The @Reference undefined callback " + callbackClassName
+ ", is not a ServiceCallback interface.");
} catch (IllegalAccessException e) {
throw new IllegalStateException("The @Reference undefined callback " + callbackClassName
+ ", is not a ServiceCallback interface.");
}
}
String key = reference.group() + "/" + reference.url() + "@" + interfaceName + ":" + reference.version() + ":"
+ reference.serialize() + ":" + reference.protocol() + ":" + reference.timeout() + ":"
+ reference.callType();
InvokerConfig<?> invokerConfig = invokerConfigs.get(key);
if (invokerConfig == null) {
invokerConfig = new InvokerConfig(referenceClass, reference.url(), reference.timeout(),
reference.callType(), reference.serialize(), callback, reference.group(), false,
reference.loadbalance(), reference.cluster(), reference.retries(), reference.timeoutRetry(),
reference.vip(), reference.version(), reference.protocol());
invokerConfig.setSecret(reference.secret());
invokerConfigs.putIfAbsent(key, invokerConfig);
invokerConfig = invokerConfigs.get(key);
}
return ServiceFactory.getService(invokerConfig);
}
private boolean isMatchPackage(String beanClassName) {
if (annotationPackages == null || annotationPackages.length == 0) {
return true;
}
for (String pkg : annotationPackages) {
if (beanClassName.startsWith(pkg)) {
return true;
}
}
return false;
}
private boolean isMatchPackage(Class type) {
String beanClassName = type.getName();
for (String pkg : annotationPackages) {
if (beanClassName.startsWith(pkg)) {
return true;
}
}
return false;
}
@Override
public void destroy() throws Exception {
}
}