/*
* Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cloudfoundry.reconfiguration.util;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.ManagedProperties;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Standard implementation of {@link PropertyAugmenter}
*/
public final class StandardPropertyAugmenter implements PropertyAugmenter {
@Override
public void augment(ConfigurableListableBeanFactory beanFactory, Class<?> beanClass, String key,
ManagedProperties additionalProperties) {
additionalProperties.setMergeEnabled(true);
for (String beanName : beanFactory.getBeanNamesForType(beanClass, true, false)) {
MutablePropertyValues propertyValues = getBeanDefinition(beanFactory, beanName).getPropertyValues();
Properties originalValue = extractOriginalValue(beanFactory, propertyValues.getPropertyValue(key));
propertyValues.addPropertyValue(key, additionalProperties.merge(originalValue));
}
}
private Properties extractOriginalValue(ConfigurableListableBeanFactory beanFactory,
PropertyValue originalProperty) {
if (originalProperty == null) {
return new ManagedProperties();
}
Object value = originalProperty.getValue();
if (value instanceof Properties) {
return (Properties) value;
} else if (value instanceof BeanDefinitionHolder) {
return extractProperties((BeanDefinitionHolder) value);
} else if (value instanceof BeanReference) {
return extractProperties((BeanReference) value, beanFactory);
} else if (value instanceof TypedStringValue) {
return extractProperties((TypedStringValue) value);
}
throw new IllegalStateException(String.format("Unable to process property '%s' of type %s",
originalProperty.getName(), value.getClass().getName()));
}
private BeanDefinition getBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
String canonicalBeanName;
if (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
canonicalBeanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
} else {
canonicalBeanName = beanName;
}
return beanFactory.getBeanDefinition(canonicalBeanName);
}
private Properties extractProperties(BeanDefinitionHolder beanDefinitionHolder) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
return getMapWrappingBeanProperties(beanDefinition);
}
private Properties extractProperties(BeanReference beanReference, ConfigurableListableBeanFactory beanFactory) {
BeanDefinition beanDefinition = getBeanDefinition(beanFactory, beanReference.getBeanName());
return getMapWrappingBeanProperties(beanDefinition);
}
private Properties extractProperties(TypedStringValue value) {
try {
Properties properties = new Properties();
properties.load(new StringReader(value.getValue()));
return properties;
} catch (IOException e) {
throw new IllegalStateException(String.format("Error loading properties from '%s'", value), e);
}
}
@SuppressWarnings("unchecked")
private Properties getMapWrappingBeanProperties(BeanDefinition beanDefinition) {
if (beanDefinition.getBeanClassName().equals(PropertiesFactoryBean.class.getName())) {
return getPropertiesFactoryBeanProperties(beanDefinition);
} else {
return convert((Map<String, String>) beanDefinition.getPropertyValues().getPropertyValue
("sourceMap").getValue());
}
}
@SuppressWarnings("unchecked")
private Properties getPropertiesFactoryBeanProperties(BeanDefinition beanDefinition) {
PropertyValues propertyValues = beanDefinition.getPropertyValues();
if (propertyValues.contains("location")) {
return loadPropertiesForLocation(propertyValues.getPropertyValue("location").getValue());
} else if (propertyValues.contains("locations")) {
return loadPropertiesForLocations(propertyValues.getPropertyValue("locations").getValue());
} else if (propertyValues.contains("properties")) {
Object value = propertyValues.getPropertyValue("properties").getValue();
if (value instanceof BeanDefinitionHolder) {
return extractProperties((BeanDefinitionHolder) value);
} else {
return convert((Map<String, String>) value);
}
}
throw new IllegalArgumentException(
"Unable to process PropertiesFactoryBean; doesn't contain either 'locations' or 'properties' property");
}
private Properties loadPropertiesForLocation(Object location) {
try {
if (location instanceof String) {
return PropertiesLoaderUtils.loadAllProperties((String) location);
} else if (location instanceof TypedStringValue) {
return PropertiesLoaderUtils.loadAllProperties(((TypedStringValue) location).getValue());
}
} catch (IOException e) {
throw new IllegalArgumentException(String.format("Unable to load properties from '%s'", location), e);
}
throw new IllegalArgumentException(String.format("Unable to process 'location' value of type %s",
location.getClass().getName()));
}
private Properties loadPropertiesForLocations(Object locations) {
List normalizedLocations;
if (locations instanceof Object[]) {
normalizedLocations = Arrays.asList((Object[]) locations);
} else if (locations instanceof List) {
normalizedLocations = (List) locations;
} else {
throw new IllegalArgumentException(String.format("Unable to process 'locations' value of type %s",
locations.getClass().getName()));
}
Properties props = new Properties();
for (Object location : normalizedLocations) {
props.putAll(loadPropertiesForLocation(location));
}
return props;
}
private Properties convert(Map<String, String> map) {
Properties properties = new Properties();
properties.putAll(map);
return properties;
}
}