Skip to content

Commit

Permalink
Reconfigure Mongo
Browse files Browse the repository at this point in the history
- Split CloudAutoStagingBeanFactoryPostProcessor into multiple classes
(without changing any functionality)

- Replace a single MongoDbFactory with one connecting to a mongo cloud
service bound to the current application

Change-Id: I22f4a9970471f4d0ddf896fd97eedb1fdc8da66b
  • Loading branch information
Jennifer Hickey committed Sep 13, 2011
1 parent d75dffc commit 8f5a207
Show file tree
Hide file tree
Showing 13 changed files with 786 additions and 339 deletions.
10 changes: 9 additions & 1 deletion auto-reconfiguration/auto-reconfiguration-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<relativePath>../auto-reconfiguration-parent</relativePath>
</parent>
<properties>
<spring-data-mongo.version>1.0.0.M4</spring-data-mongo.version>
<spring.version>2.5.6</spring.version>
</properties>
<dependencies>
Expand Down Expand Up @@ -50,6 +51,13 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>${spring-data-mongo.version}</version>
<scope>provided</scope>
</dependency>

<!-- Test -->
<dependency>
<groupId>junit</groupId>
Expand Down Expand Up @@ -130,7 +138,7 @@
</exclusion>
</exclusions>
<scope>provided</scope>
</dependency>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2009-2011 VMware, Inc.
*/
package org.cloudfoundry.reconfiguration;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;

/**
*
* A {@link Configurer} reconfigures certain beans in a specified
* {@link DefaultListableBeanFactory}
*
* @author Jennifer Hickey
*
*/
public interface Configurer {

/**
* Reconfigures beans in the specified {@link DefaultListableBeanFactory}
*
* @param beanFactory
* The {@link DefaultListableBeanFactory} containing beans which
* may need to be configured
* @return true if any beans were reconfigured
*/
boolean configure(DefaultListableBeanFactory beanFactory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
* Copyright (c) 2009-2011 VMware, Inc.
*/
package org.cloudfoundry.reconfiguration;

import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;

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.PropertiesFactoryBean;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedProperties;
import org.springframework.core.io.support.PropertiesLoaderUtils;

/**
* Replaces a bean property with value type {@link ManagedProperties} with a
* specified set of {@link ManagedProperties}
*
* @author Ramnivas Laddad
* @author Xin Li
* @author Jennifer Hickey
*
*/
public class PropertyReplacer {

/**
* Replaces a bean property with value type {@link ManagedProperties} with a
* specified set of {@link ManagedProperties}
*
* @param beanFactory
* The {@link DefaultListableBeanFactory} containing the beans to
* evaluate for property replacements
* @param beanClassName
* The class of bean containing the property to be replaced
* @param replacementPropertiesName
* The name of the Properties bean containing values to use in
* replacement
* @param propertyKey
* The name of the property whose value is to be replaced with replacement properties
*/
public void replaceProperty(DefaultListableBeanFactory beanFactory, String beanClassName,
String replacementPropertiesName, String propertyKey) {
Class<?> beanClass = loadClass(beanClassName);
if (beanClass == null) {
return;
}
try {
// TODO: Required in Grails case and need to reexamine
beanFactory.getBeanDefinition(replacementPropertiesName);
} catch (Exception ex) {
return;
}
String[] beanNames = beanFactory.getBeanNamesForType(beanClass);
for (String beanName : beanNames) {
BeanDefinition beanDefinition = getBeanDefinition(beanFactory, beanName);
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
PropertyValue originalProperties = propertyValues.getPropertyValue(propertyKey);

Properties originalPropertyValue = null;
if (originalProperties != null) {
Object value = originalProperties.getValue();
if (value instanceof Properties) {
originalPropertyValue = (Properties) originalProperties.getValue();
} else if (value instanceof BeanDefinitionHolder) {
originalPropertyValue = extractProperties((BeanDefinitionHolder) value);
} else if (value instanceof BeanReference) {
originalPropertyValue = extractProperties((BeanReference) value, beanFactory);
} else if (value instanceof TypedStringValue) {
originalPropertyValue = extractProperties((TypedStringValue) value);
} else {
throw new IllegalStateException("Unable to process property " + originalProperties.getName()
+ " of " + value.getClass() + " type");
}
} else {
originalPropertyValue = new ManagedProperties();
}

ManagedProperties replacementProperties = loadReplacementPropertyValues(beanFactory,
replacementPropertiesName);
replacementProperties.setMergeEnabled(true);
replacementProperties = (ManagedProperties) replacementProperties.merge(originalPropertyValue);
propertyValues.addPropertyValue(new PropertyValue(propertyKey, replacementProperties));
}
}

private Properties extractProperties(BeanDefinitionHolder beanDefinitionHolder) {
try {
BeanDefinition valBeanDefinition = beanDefinitionHolder.getBeanDefinition();
return getMapWrappingBeanProperties(valBeanDefinition);
} catch (Exception e) {
throw new IllegalStateException("Error processing property replacement for a BeanDefinitionHolder", e);
}
}

private Properties extractProperties(BeanReference beanReference, DefaultListableBeanFactory beanFactory) {
try {
BeanDefinition beanDefinition = getBeanDefinition(beanFactory, beanReference.getBeanName());
return getMapWrappingBeanProperties(beanDefinition);
} catch (Exception e) {
throw new IllegalStateException("Error processing property replacement for a BeanDefinitionHolder", e);
}
}

private Properties extractProperties(TypedStringValue typeStringValue) {
Object value = typeStringValue.getValue();
if (value instanceof Properties) {
return (Properties) value;
} else if (value instanceof String) {
Properties props = new Properties();
try {
props.load(new StringReader((String) value));
return props;
} catch (IOException e) {
throw new IllegalStateException("Error processing property replacement for a TypedStringValue", e);
}
} else {
throw new IllegalStateException(
"Error processing property replacement for a TypedStringValue of value type " + value.getClass());
}
}

private ManagedProperties loadReplacementPropertyValues(DefaultListableBeanFactory beanFactory,
String replacementPropertiesName) {
BeanDefinition replacementPropertiesBeanDef = beanFactory.getBeanDefinition(replacementPropertiesName);
return (ManagedProperties) replacementPropertiesBeanDef.getPropertyValues().getPropertyValue("properties")
.getValue();
}

@SuppressWarnings("unchecked")
private Properties getMapWrappingBeanProperties(BeanDefinition beanDefinition) {
if (beanDefinition.getBeanClassName().equals(PropertiesFactoryBean.class.getName())) {
try {
PropertyValues propertyValues = beanDefinition.getPropertyValues();
if (propertyValues.contains("location")) {
return loadPropertiesForLocation(propertyValues.getPropertyValue("location"));
} else if (propertyValues.contains("locations")) {
return loadPropertiesForLocations(propertyValues.getPropertyValue("locations"));
} else if (propertyValues.contains("properties")) {
Object value = propertyValues.getPropertyValue("properties").getValue();
if (value instanceof BeanDefinitionHolder) {
return extractProperties((BeanDefinitionHolder) value);
} else {
return mapToProperties((Map<String, String>) value);
}
} else {
throw new IllegalArgumentException(
"Unable to process PropertiesFactoryBean; doesn't contain either 'locations' or 'properties' property");
}
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to process PropertiesFactoryBean", ex);
}
} else {
Map<String, String> sourceMap = (Map<String, String>) beanDefinition.getPropertyValues()
.getPropertyValue("sourceMap").getValue();
return mapToProperties(sourceMap);
}
}

private Properties loadPropertiesForLocation(PropertyValue locationPV) throws IOException {
Object locationValue = locationPV.getValue();
return loadPropertiesForLocation(locationValue);
}

private Properties loadPropertiesForLocation(Object location) throws IOException {
if (location instanceof String) {
return PropertiesLoaderUtils.loadAllProperties((String) location);
} else if (location instanceof TypedStringValue) {
return PropertiesLoaderUtils.loadAllProperties(((TypedStringValue) location).getValue());
} else {
throw new IllegalArgumentException("Unable to process 'location' value of type " + location.getClass());
}
}

private Properties loadPropertiesForLocations(PropertyValue locationPV) throws IOException {
Object locationsValue = locationPV.getValue();
if (locationsValue instanceof ManagedList) {
Properties props = new Properties();
for (Object location : (ManagedList) locationsValue) {
props.putAll(loadPropertiesForLocation(location));
}
return props;
} else {
throw new IllegalArgumentException("Unable to process 'locations' value of PropertyValue "
+ locationsValue.getClass());
}
}

private Properties mapToProperties(Map<String, String> map) {
Properties properties = new Properties();
for (Map.Entry<String, String> entry : map.entrySet()) {
properties.put(entry.getKey(), entry.getValue());
}
return properties;
}

private Class<?> loadClass(String name) {
try {
return Class.forName(name);
} catch (Throwable ex) {
return null;
}
}

private BeanDefinition getBeanDefinition(DefaultListableBeanFactory beanFactory, String beanName) {
if (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
return beanFactory.getBeanDefinition(beanName);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.cloudfoundry.reconfiguration.data.document;

import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.cloudfoundry.reconfiguration.Configurer;
import org.cloudfoundry.runtime.env.CloudEnvironment;
import org.cloudfoundry.runtime.env.CloudServiceException;
import org.cloudfoundry.runtime.service.document.MongoServiceCreator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;

/**
* Implementation of {@link Configurer} that replaces a single
* {@link MongoDbFactory} with one connecting to a mongo cloud service bound to
* the current application.
*
* @author Jennifer Hickey
*
*/
public class MongoConfigurer implements Configurer {

private final Logger logger = Logger.getLogger(MongoConfigurer.class.getName());

static final String CF_MONGO_DB_FACTORY_NAME = "__cloudFoundryMongoDbFactory";

private static final String MONGO_DB_FACTORY_CLASS_NAME = "org.springframework.data.mongodb.MongoDbFactory";

private CloudEnvironment cloudEnvironment;

private MongoServiceCreator serviceCreator;

public MongoConfigurer(CloudEnvironment cloudEnvironment) {
this.cloudEnvironment = cloudEnvironment;
this.serviceCreator = new MongoServiceCreator(cloudEnvironment);
}

@Override
public boolean configure(DefaultListableBeanFactory beanFactory) {
Class<?> mongoDbFactoryClass = loadClass(MONGO_DB_FACTORY_CLASS_NAME);
if (mongoDbFactoryClass == null) {
logger.log(Level.INFO, "No MongoDbFactory class found.");
return false;
}
String[] mongoDbFactoryBeanNames = beanFactory.getBeanNamesForType(mongoDbFactoryClass);
if (mongoDbFactoryBeanNames.length == 0) {
logger.log(Level.INFO, "No MongoDbFactory found in application context");
return false;
} else if (mongoDbFactoryBeanNames.length > 1) {
logger.log(Level.INFO, "More than 1 (" + mongoDbFactoryBeanNames.length
+ ") MongoDbFactory beans found in application context. Skipping autostaging.");
return false;
}
for (Map<String, Object> service : cloudEnvironment.getServices()) {
String label = (String) service.get("label");
if (label == null) {
continue;
}
if (label.startsWith("mongodb")) {
try {
beanFactory.registerSingleton(CF_MONGO_DB_FACTORY_NAME,
serviceCreator.createSingletonService().service);
beanFactory.removeBeanDefinition(mongoDbFactoryBeanNames[0]);
beanFactory.registerAlias(CF_MONGO_DB_FACTORY_NAME, mongoDbFactoryBeanNames[0]);
return true;
} catch (CloudServiceException ex) {
logger.log(Level.INFO, "Multiple mongo services found. Skipping autostaging", ex);
return false;
}
}
}
logger.log(Level.INFO, "No mongo service found. Skipping autostaging");
return false;
}

private Class<?> loadClass(String name) {
try {
return Class.forName(name);
} catch (Throwable ex) {
return null;
}
}

public void setServiceCreator(MongoServiceCreator serviceCreator) {
this.serviceCreator = serviceCreator;
}
}
Loading

0 comments on commit 8f5a207

Please sign in to comment.