package com.revolsys.spring.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.MapFactoryBean;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.StringValueResolver;
import com.revolsys.logging.Logs;
import com.revolsys.spring.BeanReference;
import com.revolsys.spring.TargetBeanFactoryBean;
import com.revolsys.spring.factory.Parameter;
import com.revolsys.spring.util.PlaceholderResolvingStringValueResolver;
public class BeanConfigurrer
implements BeanFactoryPostProcessor, ApplicationContextAware, BeanNameAware, PriorityOrdered {
public static final Pattern KEY_PATTERN = Pattern
.compile("(\\w[\\w\\d]*)(?:(?:\\[([\\w\\d]+)\\])|(?:\\.([\\w\\d]+)))?");
public static void newParameterBeanDefinition(final ConfigurableListableBeanFactory factory,
final String beanName, final Object value) {
final GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(Parameter.class);
final MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
propertyValues.add("type", value.getClass());
propertyValues.add("value", value);
((DefaultListableBeanFactory)factory).registerBeanDefinition(beanName, beanDefinition);
}
/**
* Apply the given property value to the corresponding bean.
*/
public static void setAttributeValue(final ConfigurableListableBeanFactory factory,
final String beanName, final String property, final Object value) {
final ClassLoader classLoader = factory.getBeanClassLoader();
BeanDefinition bd = factory.getBeanDefinition(beanName);
while (bd.getOriginatingBeanDefinition() != null) {
bd = bd.getOriginatingBeanDefinition();
}
final MutablePropertyValues propertyValues = bd.getPropertyValues();
PropertyValue propertyValue = new PropertyValue(property, value);
final String beanClassName = bd.getBeanClassName();
if (!TargetBeanFactoryBean.class.getName().equals(beanClassName)) {
if (Parameter.class.getName().equals(beanClassName)) {
final PropertyValue typeValue = propertyValues.getPropertyValue("type");
if (typeValue != null) {
final String typeClassName = typeValue.getValue().toString();
try {
final Class<?> typeClass = Class.forName(typeClassName, true, classLoader);
final Object convertedValue = new SimpleTypeConverter().convertIfNecessary(value,
typeClass);
propertyValue = new PropertyValue(property, convertedValue);
} catch (final Throwable e) {
Logs.error(BeanConfigurrer.class,
"Unable to set " + beanName + "." + property + "=" + value, e);
}
}
}
propertyValues.addPropertyValue(propertyValue);
}
}
private ApplicationContext applicationContext;
private final Map<String, Object> attributes = new LinkedHashMap<>();
private String beanName;
private boolean ignoreInvalidKeys = true;
private boolean ignoreUnresolvablePlaceholders = true;
private int order = Ordered.LOWEST_PRECEDENCE;
public BeanConfigurrer() {
}
public BeanConfigurrer(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public BeanConfigurrer(final ApplicationContext applicationContext,
final Map<String, Object> attributes) {
this.applicationContext = applicationContext;
setAttributes(attributes);
}
public String getBeanName() {
return this.beanName;
}
public Map<String, Object> getFields() {
return this.attributes;
}
@Override
public int getOrder() {
return this.order;
}
public boolean isIgnoreInvalidKeys() {
return this.ignoreInvalidKeys;
}
public boolean isIgnoreUnresolvablePlaceholders() {
return this.ignoreUnresolvablePlaceholders;
}
@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory)
throws BeansException {
processPlaceholderAttributes(beanFactory, this.attributes);
processOverrideAttributes(beanFactory, this.attributes);
}
/**
* Process the given key as 'beanName.property' entry.
*/
protected void processOverride(final ConfigurableListableBeanFactory factory, final String key,
Object value) {
try {
if (value instanceof BeanReference) {
final BeanReference reference = (BeanReference)value;
value = reference.getBean();
}
final Matcher matcher = BeanConfigurrer.KEY_PATTERN.matcher(key);
if (matcher.matches()) {
final String beanName = matcher.group(1);
final String mapKey = matcher.group(2);
final String propertyName = matcher.group(3);
if (mapKey == null) {
if (propertyName == null) {
if (factory.containsBean(beanName)) {
BeanDefinition beanDefinition = factory.getBeanDefinition(beanName);
try {
final ClassLoader classLoader = this.applicationContext.getClassLoader();
final String beanClassName = beanDefinition.getBeanClassName();
final Class<?> beanClass = Class.forName(beanClassName, true, classLoader);
if (Parameter.class.isAssignableFrom(beanClass)) {
while (beanDefinition.getOriginatingBeanDefinition() != null) {
beanDefinition = beanDefinition.getOriginatingBeanDefinition();
}
final MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
PropertyValue propertyValue = new PropertyValue("value", value);
final PropertyValue typeValue = propertyValues.getPropertyValue("type");
if (typeValue != null) {
try {
final Class<?> typeClass;
final Object typeValueObject = typeValue.getValue();
if (typeValueObject instanceof Class<?>) {
typeClass = (Class<?>)typeValueObject;
} else {
final String typeClassName = typeValueObject.toString();
typeClass = Class.forName(typeClassName, true, classLoader);
}
final Object convertedValue = new SimpleTypeConverter()
.convertIfNecessary(value, typeClass);
propertyValue = new PropertyValue("value", convertedValue);
} catch (final Throwable e) {
Logs.error(this, "Unable to set " + beanName + ".value=" + value, e);
}
}
propertyValues.addPropertyValue(propertyValue);
}
} catch (final ClassNotFoundException e) {
Logs.error(this, "Unable to set " + beanName + ".value=" + value, e);
}
} else if (value != null) {
newParameterBeanDefinition(factory, beanName, value);
}
} else {
setAttributeValue(factory, beanName, propertyName, value);
Logs.debug(this, "Property '" + key + "' set to value [" + value + "]");
}
} else if (propertyName == null) {
setMapValue(factory, key, beanName, mapKey, value);
} else {
Logs.error(this, "Invalid syntax unable to set " + key + "=" + value);
}
}
} catch (final BeansException ex) {
final String msg = "Could not process key '" + key + "' in PropertyOverrideConfigurer";
if (!this.ignoreInvalidKeys) {
throw new BeanInitializationException(msg, ex);
}
Logs.debug(this, msg, ex);
}
}
protected void processOverrideAttributes(final ConfigurableListableBeanFactory beanFactory,
final Map<String, Object> attributes) {
for (final Entry<String, Object> attribute : attributes.entrySet()) {
final String key = attribute.getKey();
final Object value = attribute.getValue();
processOverride(beanFactory, key, value);
}
}
protected void processPlaceholderAttributes(final ConfigurableListableBeanFactory beanFactory,
final Map<String, Object> attributes) throws BeansException {
final Map<String, Object> attributeMap = new LinkedHashMap<>();
for (final Entry<String, Object> entry : attributes.entrySet()) {
final String key = entry.getKey();
final Object value = entry.getValue();
if (!(value instanceof BeanReference)) {
attributeMap.put(key, value);
}
}
final StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver("${", "}",
this.ignoreUnresolvablePlaceholders, null, attributeMap);
final BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
final String[] beanNames = beanFactory.getBeanDefinitionNames();
for (int i = 0; i < beanNames.length; i++) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file
// locations.
if (!(beanNames[i].equals(this.beanName) && beanFactory.equals(this.applicationContext))) {
final BeanDefinition bd = beanFactory.getBeanDefinition(beanNames[i]);
try {
visitor.visitBeanDefinition(bd);
} catch (final BeanDefinitionStoreException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanNames[i],
ex.getMessage());
}
}
}
// NEW in Spring 2.5: resolve placeholders in alias target names and aliases
// as well.
beanFactory.resolveAliases(valueResolver);
}
protected void processPlaceholderAttributes(final ConfigurableListableBeanFactory beanFactory,
final String beanName, final Map<String, Object> attributes) throws BeansException {
final StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver("${", "}",
this.ignoreUnresolvablePlaceholders, null, attributes);
final BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file
// locations.
if (!(beanName.equals(this.beanName) && beanFactory.equals(this.applicationContext))) {
final BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
try {
visitor.visitBeanDefinition(bd);
} catch (final BeanDefinitionStoreException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
ex.getMessage());
}
}
// NEW in Spring 2.5: resolve placeholders in alias target names and aliases
// as well.
beanFactory.resolveAliases(valueResolver);
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
protected void setAttribute(final String name, final Object value) {
this.attributes.put(name, value);
}
public void setAttributes(final Map<String, ? extends Object> attributes) {
this.attributes.clear();
if (attributes != null) {
this.attributes.putAll(attributes);
}
}
@Override
public void setBeanName(final String beanName) {
this.beanName = beanName;
}
/**
* Set whether to ignore invalid keys. Default is "false".
* <p>
* If you ignore invalid keys, keys that do not follow the 'beanName.property'
* format will just be logged as warning. This allows to have arbitrary other
* keys in a properties file.
*/
public void setIgnoreInvalidKeys(final boolean ignoreInvalidKeys) {
this.ignoreInvalidKeys = ignoreInvalidKeys;
}
public void setIgnoreUnresolvablePlaceholders(final boolean ignoreUnresolvablePlaceholders) {
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
public void setMapValue(final ConfigurableListableBeanFactory factory, final String key,
final String beanName, final String mapKey, final Object value) {
final BeanDefinition beanDefinition = factory.getBeanDefinition(beanName);
final String beanClassName = beanDefinition.getBeanClassName();
try {
final ClassLoader classLoader = this.applicationContext.getClassLoader();
final Class<?> beanClass = Class.forName(beanClassName, true, classLoader);
if (MapFactoryBean.class.isAssignableFrom(beanClass)) {
final MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
final PropertyValue sourceMapProperty = propertyValues.getPropertyValue("sourceMap");
@SuppressWarnings("unchecked")
final Map<Object, Object> sourceMap = (Map<Object, Object>)sourceMapProperty.getValue();
boolean found = false;
for (final Entry<Object, Object> entry : sourceMap.entrySet()) {
final Object mapEntryKey = entry.getKey();
if (mapEntryKey instanceof TypedStringValue) {
final TypedStringValue typedKey = (TypedStringValue)mapEntryKey;
if (typedKey.getValue().equals(mapKey)) {
entry.setValue(value);
found = true;
}
}
}
if (!found) {
sourceMap.put(new TypedStringValue(mapKey), value);
}
} else if (!TargetBeanFactoryBean.class.isAssignableFrom(beanClass)) {
Logs.error(this, "Bean class must be a MapFactoryBean, unable to set " + key + "=" + value);
}
} catch (final ClassNotFoundException e) {
Logs.error(this, "Unable to set " + key + "=" + value, e);
}
}
public void setOrder(final int order) {
this.order = order;
}
}