package com.github.bingoohuang.springrestclient.spring;
import lombok.val;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.StringUtils;
import static org.springframework.util.Assert.notNull;
public class SpringRestClientScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
/*
* This property lets you set the base package for your mapper interface files.
* <p/>
* You can set more than one package by using a semicolon or comma as a separator.
* <p/>
* Mappers will be searched for recursively starting in the specified package(s).
*
* @param basePackage base package name
*/
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
this.processPropertyPlaceHolders = processPropertyPlaceHolders;
}
/**
* {@inheritDoc}
*/
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* {@inheritDoc}
*/
public void setBeanName(String name) {
this.beanName = name;
}
/**
* Gets beanNameGenerator to be used while running the scanner.
*
* @return the beanNameGenerator BeanNameGenerator that has been configured
*/
public BeanNameGenerator getNameGenerator() {
return nameGenerator;
}
/**
* Sets beanNameGenerator to be used while running the scanner.
*
* @param nameGenerator the beanNameGenerator to set
*/
public void setNameGenerator(BeanNameGenerator nameGenerator) {
this.nameGenerator = nameGenerator;
}
/**
* {@inheritDoc}
*/
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
/**
* {@inheritDoc}
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}
/**
* {@inheritDoc}
*/
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathSpringRestClientScanner scanner = new ClassPathSpringRestClientScanner(registry);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
/*
* BeanDefinitionRegistries are called early in application startup, before
* BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
* loaded and any property substitution of this class' properties will fail. To avoid this, find
* any PropertyResourceConfigurers defined in the context and run them on this class' bean
* definition. Then update the values.
*/
private void processPropertyPlaceHolders() {
val prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
val mapperScannerBean = ((GenericApplicationContext) applicationContext)
.getBeanFactory().getBeanDefinition(beanName);
// PropertyResourceConfigurer does not expose any methods to explicitly perform
// property placeholder substitution. Instead, create a BeanFactory that just
// contains this mapper scanner and post process the factory.
val factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition(beanName, mapperScannerBean);
for (PropertyResourceConfigurer prc : prcs.values()) {
prc.postProcessBeanFactory(factory);
}
val values = mapperScannerBean.getPropertyValues();
this.basePackage = updatePropertyValue("basePackage", values);
}
}
private String updatePropertyValue(String propertyName, PropertyValues values) {
PropertyValue property = values.getPropertyValue(propertyName);
if (property == null) {
return null;
}
Object value = property.getValue();
if (value == null) {
return null;
} else if (value instanceof String) {
return value.toString();
} else if (value instanceof TypedStringValue) {
return ((TypedStringValue) value).getValue();
} else {
return null;
}
}
}