MOB-864: Propagation of installation / dev environment settings to subsystems plus subsystem framework extensions for composite properties

- Set of overridable properties now centralized to new global-properties bean and referenced by repository-properties, hibernateConfigProperties and subsystems
- Installer defaults can now be specified in classpath:alfresco-global.properties
- A special BeanFactoryPostProcessor ensures backward compatibility with existing alfresco/extension/*-context.xml files overriding repository-properties or hibernateConfigProperties.
- Subsystems pick up initial property values from global-properties. Placeholders expanded.
- Messages now output when subsystems stopped and started
- Object names lists to allow better hierarchical organisation
- Composite properties now supported by child application contexts
   - Materialized in context.xml as ListFactoryBeans - lists of beans
   - Configured values injected before application context started
   - Configurable via alfresco-global.properties or JMX

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14351 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Dave Ward
2009-05-18 15:34:46 +00:00
parent f32a5c3b8e
commit b58bd8a5ea
15 changed files with 1098 additions and 251 deletions

View File

@@ -24,37 +24,76 @@
*/
package org.alfresco.repo.management.subsystems;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* A base class for {@link PropertyBackedBean}s. Gets its category from its Spring bean name and automatically destroys
* itself on server shutdown. Communicates its creation and destruction to a {@link PropertyBackedBeanRegistry}.
* A base class for {@link PropertyBackedBean}s. Gets its category from its Spring bean name and automatically
* propagates and resolves property defaults on initialization. Automatically destroys itself on server shutdown.
* Communicates its creation and destruction to a {@link PropertyBackedBeanRegistry}.
*
* @author dward
*/
public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, InitializingBean, DisposableBean,
BeanNameAware
public abstract class AbstractPropertyBackedBean implements PropertyBackedBean, ApplicationContextAware,
ApplicationListener, InitializingBean, DisposableBean, BeanNameAware
{
/** The default ID. */
protected static final String DEFAULT_ID = "default";
/** The root component of the default ID. */
protected static final String DEFAULT_ID_ROOT = "default";
/** The registry. */
/** The default ID (when we do not expect there to be more than one instance within a category). */
protected static final List<String> DEFAULT_ID = Collections
.singletonList(AbstractPropertyBackedBean.DEFAULT_ID_ROOT);
/** The parent application context. */
private ApplicationContext parent;
/** The registry of all property backed beans. */
private PropertyBackedBeanRegistry registry;
/** The id. */
private String id = DEFAULT_ID;
/** The hierarchical id. Must be unique within the category. */
private List<String> id = AbstractPropertyBackedBean.DEFAULT_ID;
/** The category. */
private String category;
/** Should the application context be started on startup of the parent application?. */
private boolean autoStart;
/** Property defaults provided by the installer or System properties. */
private Properties propertyDefaults;
/** Resolves placeholders in the property defaults. */
private DefaultResolver defaultResolver = new DefaultResolver();
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.
* ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.parent = applicationContext;
}
/**
* Sets the registry.
* Sets the registry of all property backed beans.
*
* @param registry
* the registry to set
* the registry of all property backed beans
*/
public void setRegistry(PropertyBackedBeanRegistry registry)
{
@@ -62,13 +101,13 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean,
}
/**
* Gets the registry.
* Gets the registry of all property backed beans.
*
* @return the registry
* @return the registry of all property backed beans
*/
public PropertyBackedBeanRegistry getRegistry()
protected PropertyBackedBeanRegistry getRegistry()
{
return registry;
return this.registry;
}
/*
@@ -86,17 +125,87 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean,
* @param id
* the id to set
*/
public void setId(String id)
public void setId(List<String> id)
{
this.id = id;
}
/**
* Indicates whether the bean should be started on startup of the parent application context.
*
* @param autoStart
* <code>true</code> if the bean should be started on startup of the parent application context
*/
public void setAutoStart(boolean autoStart)
{
this.autoStart = autoStart;
}
/**
* Sets the property defaults provided by the installer or System properties.
*
* @param propertyDefaults
* the property defaults
*/
public void setPropertyDefaults(Properties propertyDefaults)
{
this.propertyDefaults = propertyDefaults;
}
/**
* Gets the property defaults provided by the installer or System properties.
*
* @return the property defaults
*/
protected Properties getPropertyDefaults()
{
return this.propertyDefaults;
}
/**
* Resolves the default value of a property, if there is one, expanding placholders as necessary.
*
* @param name
* the property name
* @return the resolved default value or <code>null</code> if there isn't one
*/
protected String resolveDefault(String name)
{
String value = this.propertyDefaults.getProperty(name);
if (value != null)
{
value = this.defaultResolver.resolveValue(value);
}
return value == null || value.length() == 0 ? null : value;
}
/**
* Gets the parent application context.
*
* @return the parent application context
*/
protected ApplicationContext getParent()
{
return this.parent;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception
{
// Override default settings using corresponding global defaults (this allows installer settings
// to propagate through)
for (String property : getPropertyNames())
{
String value = resolveDefault(property);
if (value != null)
{
setProperty(property, value);
}
}
this.registry.register(this);
}
@@ -104,7 +213,7 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean,
* (non-Javadoc)
* @see org.alfresco.repo.management.SelfDescribingBean#getId()
*/
public String getId()
public List<String> getId()
{
return this.id;
}
@@ -145,4 +254,56 @@ public abstract class AbstractPropertyBackedBean implements PropertyBackedBean,
{
return true;
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getDescription(java.lang.String)
*/
public String getDescription(String name)
{
return isUpdateable(name) ? "Editable Property " + name : "Read-only Property " + name;
}
/*
* (non-Javadoc)
* @see
* org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
public void onApplicationEvent(ApplicationEvent event)
{
if (this.autoStart && event instanceof ContextRefreshedEvent && event.getSource() == this.parent)
{
start();
}
}
/**
* Uses a Spring {@link PropertyPlaceholderConfigurer} to resolve placeholders in the property defaults. This means
* that placeholders need not be displayed in the configuration UI or JMX console.
*/
public class DefaultResolver extends PropertyPlaceholderConfigurer
{
/**
* Instantiates a new default resolver.
*/
public DefaultResolver()
{
setIgnoreUnresolvablePlaceholders(true);
}
/**
* Expands the given value, resolving any ${} placeholders using the property defaults
*
* @param val
* the value to expand
* @return the expanded value
*/
public String resolveValue(String val)
{
return AbstractPropertyBackedBean.this.propertyDefaults == null ? null : parseStringValue(val,
AbstractPropertyBackedBean.this.propertyDefaults, new HashSet<Object>());
}
}
}

View File

@@ -25,25 +25,32 @@
package org.alfresco.repo.management.subsystems;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
import org.alfresco.config.JBossEnabledResourcePatternResolver;
import org.alfresco.config.JndiPropertiesFactoryBean;
import org.alfresco.repo.imap.config.ImapConfigBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
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.config.ListFactoryBean;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
/**
@@ -56,56 +63,102 @@ import org.springframework.core.io.support.ResourcePatternResolver;
* The factory will search for a Spring application context in the classpath using the following patterns in order:
* <ul>
* <li>alfresco/subsystems/&lt;category>/&lt;typeName>/*-context.xml</li>
* <li>alfresco/extension/subsystems/&lt;category>/&lt;typeName>/&lt;id/*-context.xml</li>
* <li>alfresco/extension/subsystems/&lt;category>/&lt;typeName>/&lt;id>/*-context.xml</li>
* </ul>
* The child application context may use <code>${}</code> placeholders, and will be configured with a
* {@link PropertyPlaceholderConfigurer} initialised with properties files found in classpath matching the following
* {@link PropertyPlaceholderConfigurer} initialized with properties files found in classpath matching the following
* patterns in order:
* <ul>
* <li>alfresco/subsystems/&lt;category>/&lt;typeName>/*.properties</li>
* <li>alfresco/extension/subsystems/&lt;category>/&lt;typeName>/&lt;id/*.properties</li>
* <li>alfresco/extension/subsystems/&lt;category>/&lt;typeName>/&lt;id>/*.properties</li>
* </ul>
* This means that the extension classpath can be used to provide instance-specific overrides to product default
* settings. Of course, if you are using the Enterprise edition, you might want to use a JMX client such as JConsole to
* edit the settings instead!
* <p>
* For advanced purposes, the class also allows management of 'composite' properties, that is properties that can be
* populated with a sequence of zero or more objects, themselves registered as property-backed beans. Using the
* <code>compositePropertyTypes</code> property you can register a Map of property names to Java Bean classes. Each
* property named in this map will then be materialized as a 'composite' property.
* <p>
* Composite property settings are best controlled either through a JMX console (Enterprise edition only) or
* /alfresco-global.properties in the classpath (the replacement to /alfresco/extension/custom-repository.properties).
* For example, suppose "imap.server.mountPoints" was registered as a composite property of type {@link ImapConfigBean}.
* You can then use the property to configure a list of {@link ImapConfigBean}s. First you specify in the property's
* value a list of zero or more 'instance names'. Each name must be unique within the property.
* <p>
* <code>imap.server.mountPoints=Repository_virtual,Repository_archive</code>
* <p>
* Then, by magic you have two separate instances of {@link ImapConfigBean} whose properties you can address through an
* extended set of properties prefixed by "imap.server.mountPoints".
* <p>
* To set a property on one of the instances, you append ".value.&lt;instance name>.&lt;bean property name>" to the
* parent property name. For example:
* <p>
* <code>imap.server.mountPoints.value.Repository_virtual.mode=virtual</code>
* <p>
* To specify a default value for a property on all instances of the bean, you append ".default.&lt;bean property name>"
* to the parent property name. For example:
* <p>
* <code>imap.server.mountPoints.default.store=${spaces.store}</code>
* <p>
* Note that it's perfectly valid to use placeholders in property values that will be resolved from other global
* properties.
* <p>
* In order to actually utilize this configurable list of beans in your child application context, you simply need to
* declare a {@link ListFactoryBean} whose ID is the same name as the property. For example:
*
* <pre>
* &lt;bean id=&quot;imap.server.mountPoints&quot; class=&quot;org.springframework.beans.factory.config.ListFactoryBean&quot;&gt;
* &lt;property name=&quot;sourceList&quot;&gt;
* &lt;!-- Whatever you declare in here will get replaced by the property value list --&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* </pre>
*
* Then, when the application context is started and before that bean is initialized, it will be given the current
* configured list of values for the composite property. Magic! This all sounds like a complex, yet primitive
* replacement for Spring, but it means you can do powerful things to reconfigure the system through an admin UI rather
* than editing XML.
*
* @author dward
*/
public class ChildApplicationContextFactory extends AbstractPropertyBackedBean implements ApplicationContextAware,
ApplicationListener, ApplicationContextFactory
public class ChildApplicationContextFactory extends AbstractPropertyBackedBean implements ApplicationContextFactory
{
/** The name of the special read-only property containing the type name. */
private static final String TYPE_NAME_PROPERTY = "$type";
/** The Constant PROPERTIES_SUFFIX. */
/** The suffix to the property file search path. */
private static final String PROPERTIES_SUFFIX = "/*.properties";
/** The Constant CONTEXT_SUFFIX. */
/** The suffix to the context file search path. */
private static final String CONTEXT_SUFFIX = "/*-context.xml";
/** The Constant CLASSPATH_PREFIX. */
/** The prefix to default file search paths. */
private static final String CLASSPATH_PREFIX = "classpath*:alfresco/subsystems/";
/** The Constant EXTENSION_CLASSPATH_PREFIX. */
/** The prefix to extension file search paths. */
private static final String EXTENSION_CLASSPATH_PREFIX = "classpath*:alfresco/extension/subsystems/";
/** The logger. */
private static Log logger = LogFactory.getLog(ChildApplicationContextFactory.class);
/** The parent. */
private ApplicationContext parent;
/** The properties. */
/** The properties to be used in placeholder expansion. */
private Properties properties;
/** The application context. */
/** The child application context. */
private ClassPathXmlApplicationContext applicationContext;
/** The auto start. */
private boolean autoStart;
/** The type name. */
private String typeName;
/** The registered composite propertes and their types. */
private Map<String, Class<?>> compositePropertyTypes = Collections.emptyMap();
/** The composite property values. */
private Map<String, Map<String, CompositeDataBean>> compositeProperties = new TreeMap<String, Map<String, CompositeDataBean>>();
/**
* Default constructor for container construction.
*/
@@ -120,6 +173,8 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
* the parent application context
* @param registry
* the registry of property backed beans
* @param propertyDefaults
* property defaults provided by the installer or System properties
* @param category
* the category
* @param typeName
@@ -130,10 +185,11 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
* Signals that an I/O exception has occurred.
*/
public ChildApplicationContextFactory(ApplicationContext parent, PropertyBackedBeanRegistry registry,
String category, String typeName, String id) throws IOException
Properties propertyDefaults, String category, String typeName, List<String> id) throws IOException
{
setApplicationContext(parent);
setRegistry(registry);
setPropertyDefaults(propertyDefaults);
setBeanName(category);
setTypeName(typeName);
setId(id);
@@ -152,28 +208,6 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
}
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.
* ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.parent = applicationContext;
}
/**
* Indicates whether the application context be started on startup of the parent application context.
*
* @param autoStart
* <code>true</code> if the application context should be started on startup of the parent application
* context
*/
public void setAutoStart(boolean autoStart)
{
this.autoStart = autoStart;
}
/**
* Sets the type name.
*
@@ -195,6 +229,19 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
return this.typeName;
}
/**
* Registers a set of composite propertes and their types.
*
* @param compositePropertyTypes
* a map of property names to Java classes. The classes should follow standard Java Bean conventions. If
* the class implements {@link BeanNameAware} the instance name will be propagated to the
* <code>beanName</code> property automatically.
*/
public void setCompositePropertyTypes(Map<String, Class<?>> compositePropertyTypes)
{
this.compositePropertyTypes = compositePropertyTypes;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
@@ -204,38 +251,31 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
{
if (getTypeName() == null)
{
setTypeName(getId());
setTypeName(getId().get(0));
}
// Load the property defaults
PropertiesFactoryBean factory = new PropertiesFactoryBean();
Resource[] baseResources = this.parent.getResources(ChildApplicationContextFactory.CLASSPATH_PREFIX
+ getCategory() + '/' + getTypeName() + ChildApplicationContextFactory.PROPERTIES_SUFFIX);
// Allow overrides from the extension classpath
Resource[] extensionResources = this.parent
.getResources(ChildApplicationContextFactory.EXTENSION_CLASSPATH_PREFIX + getCategory() + '/'
+ getTypeName() + '/' + getId() + '/' + ChildApplicationContextFactory.PROPERTIES_SUFFIX);
Resource[] combinedResources;
if (baseResources.length == 0)
{
combinedResources = extensionResources;
}
else if (extensionResources.length == 0)
{
combinedResources = baseResources;
}
else
{
combinedResources = new Resource[baseResources.length + extensionResources.length];
System.arraycopy(baseResources, 0, combinedResources, 0, baseResources.length);
System.arraycopy(extensionResources, 0, combinedResources, baseResources.length, extensionResources.length);
}
factory.setLocations(combinedResources);
factory.setLocations(getParent().getResources(
ChildApplicationContextFactory.CLASSPATH_PREFIX + getCategory() + '/' + getTypeName()
+ ChildApplicationContextFactory.PROPERTIES_SUFFIX));
factory.afterPropertiesSet();
this.properties = (Properties) factory.getObject();
// Now let the superclass propagate default settings from the global properties and register us
super.afterPropertiesSet();
// Apply any property overrides from the extension classpath and also allow system properties and JNDI to
// override
JndiPropertiesFactoryBean overrideFactory = new JndiPropertiesFactoryBean();
overrideFactory.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE);
overrideFactory.setLocations(getParent().getResources(
ChildApplicationContextFactory.EXTENSION_CLASSPATH_PREFIX + getCategory() + '/' + getTypeName() + '/'
+ getId() + '/' + ChildApplicationContextFactory.PROPERTIES_SUFFIX));
overrideFactory.setProperties(this.properties);
overrideFactory.afterPropertiesSet();
this.properties = (Properties) overrideFactory.getObject();
}
/*
@@ -247,6 +287,7 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
{
Set<String> result = new TreeSet<String>(((Map) this.properties).keySet());
result.add(ChildApplicationContextFactory.TYPE_NAME_PROPERTY);
result.addAll(this.compositePropertyTypes.keySet());
return result;
}
@@ -260,6 +301,24 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
{
return getTypeName();
}
else if (this.compositePropertyTypes.containsKey(name))
{
Map<String, CompositeDataBean> beans = this.compositeProperties.get(name);
if (beans != null)
{
StringBuilder list = new StringBuilder(100);
for (String id : beans.keySet())
{
if (list.length() > 0)
{
list.append(',');
}
list.append(id);
}
return list.toString();
}
return "";
}
else
{
return this.properties.getProperty(name);
@@ -278,7 +337,12 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
throw new IllegalStateException("Illegal write to property \""
+ ChildApplicationContextFactory.TYPE_NAME_PROPERTY + "\"");
}
if (value == null)
Class<?> type = this.compositePropertyTypes.get(name);
if (type != null)
{
updateCompositeProperty(name, value, type);
}
else if (value == null)
{
this.properties.remove(name);
}
@@ -288,6 +352,79 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
}
}
/**
* Updates a composite property with a new list of instance names. Properties of those instances that existed
* previously will be preserved. Instances that no longer exist will be destroyed. New instances will be brought
* into life with default values, as described in the class description.
*
* @param name
* the composite property name
* @param value
* a list of bean instance IDs
* @param type
* the bean class
*/
private void updateCompositeProperty(String name, String value, Class<?> type)
{
// Retrieve the map of existing values of this property
Map<String, CompositeDataBean> propertyValues = this.compositeProperties.get(name);
if (propertyValues == null)
{
if (value == null)
{
return;
}
propertyValues = Collections.emptyMap();
}
try
{
Map<String, CompositeDataBean> newPropertyValues = new LinkedHashMap<String, CompositeDataBean>(11);
if (value != null)
{
StringTokenizer tkn = new StringTokenizer(value, ", \t\n\r\f");
while (tkn.hasMoreTokens())
{
String id = tkn.nextToken();
// Generate a unique ID within the category
List<String> childId = new ArrayList<String>(3);
childId.addAll(getId());
childId.add(name);
childId.add(id);
// Look out for new or updated children
CompositeDataBean child = propertyValues.get(id);
if (child == null)
{
child = new CompositeDataBean(getParent(), this, getRegistry(), getPropertyDefaults(),
getCategory(), type, childId);
}
newPropertyValues.put(id, child);
}
}
// Destroy any children that have been removed
Set<String> idsToRemove = new TreeSet<String>(propertyValues.keySet());
idsToRemove.removeAll(newPropertyValues.keySet());
for (String id : idsToRemove)
{
CompositeDataBean child = propertyValues.get(id);
child.destroy(true);
}
this.compositeProperties.put(name, newPropertyValues);
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#isUpdateable(java.lang.String)
@@ -299,47 +436,33 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
return !name.equals(ChildApplicationContextFactory.TYPE_NAME_PROPERTY);
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#getDescription(java.lang.String)
*/
@Override
public String getDescription(String name)
{
return name.equals(ChildApplicationContextFactory.TYPE_NAME_PROPERTY) ? "Read-only subsystem type name"
: this.compositePropertyTypes.containsKey(name) ? "Comma separated list of child object names" : super
.getDescription(name);
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start()
*/
public synchronized void start()
{
this.applicationContext = new ClassPathXmlApplicationContext(new String[]
// This is where we actually create and start a child application context based on the configured properties.
if (this.applicationContext == null)
{
ChildApplicationContextFactory.CLASSPATH_PREFIX + getCategory() + '/' + getTypeName()
+ ChildApplicationContextFactory.CONTEXT_SUFFIX,
ChildApplicationContextFactory.EXTENSION_CLASSPATH_PREFIX + getCategory() + '/' + getTypeName() + '/'
+ getId() + '/' + ChildApplicationContextFactory.CONTEXT_SUFFIX
}, false, this.parent)
{
/*
* (non-Javadoc)
* @see org.springframework.context.support.AbstractApplicationContext#getResourcePatternResolver()
*/
@Override
protected ResourcePatternResolver getResourcePatternResolver()
{
return new JBossEnabledResourcePatternResolver(this);
}
};
// Add a property placeholder configurer, with the subsystem-scoped default properties
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setProperties(this.properties);
configurer.setIgnoreUnresolvablePlaceholders(true);
this.applicationContext.addBeanFactoryPostProcessor(configurer);
// Add all the post processors of the parent, e.g. to make sure system placeholders get expanded properly
for (Object postProcessor : this.parent.getBeansOfType(BeanFactoryPostProcessor.class).values())
{
this.applicationContext.addBeanFactoryPostProcessor((BeanFactoryPostProcessor) postProcessor);
ChildApplicationContextFactory.logger.info("Starting '" + getCategory() + "' subsystem, ID: " + getId());
this.applicationContext = new ChildApplicationContext();
this.applicationContext.refresh();
ChildApplicationContextFactory.logger.info("Startup of '" + getCategory() + "' subsystem, ID: " + getId()
+ " complete");
}
this.applicationContext.setClassLoader(this.parent.getClassLoader());
this.applicationContext.refresh();
}
/*
@@ -350,8 +473,29 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
{
if (this.applicationContext != null)
{
ChildApplicationContextFactory.logger.info("Stopping '" + getCategory() + "' subsystem, ID: " + getId());
this.applicationContext.close();
this.applicationContext = null;
ChildApplicationContextFactory.logger.info("Stopped '" + getCategory() + "' subsystem, ID: " + getId());
}
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#destroy(boolean)
*/
@Override
public void destroy(boolean permanent)
{
super.destroy(permanent);
// Cascade the destroy / shutdown
for (Map<String, CompositeDataBean> beans : this.compositeProperties.values())
{
for (CompositeDataBean bean : beans.values())
{
bean.destroy(permanent);
}
}
}
@@ -361,24 +505,104 @@ public class ChildApplicationContextFactory extends AbstractPropertyBackedBean i
*/
public synchronized ApplicationContext getApplicationContext()
{
if (this.applicationContext == null)
{
start();
}
start();
return this.applicationContext;
}
/*
* (non-Javadoc)
* @see
* org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
/**
* A specialized application context class with the power to propagate simple and composite property values into
* beans before they are initialized.
*
* @author dward
*/
public void onApplicationEvent(ApplicationEvent event)
private class ChildApplicationContext extends ClassPathXmlApplicationContext
{
// Automatically refresh the application context on boot up, if required
if (this.autoStart && event instanceof ContextRefreshedEvent && event.getSource() == this.parent)
/**
* The Constructor.
*
* @throws BeansException
* the beans exception
*/
private ChildApplicationContext() throws BeansException
{
getApplicationContext();
super(new String[]
{
ChildApplicationContextFactory.CLASSPATH_PREFIX + getCategory() + '/' + getTypeName()
+ ChildApplicationContextFactory.CONTEXT_SUFFIX,
ChildApplicationContextFactory.EXTENSION_CLASSPATH_PREFIX + getCategory() + '/' + getTypeName() + '/'
+ getId() + '/' + ChildApplicationContextFactory.CONTEXT_SUFFIX
}, false, ChildApplicationContextFactory.this.getParent());
// Add a property placeholder configurer, with the subsystem-scoped default properties
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setProperties(ChildApplicationContextFactory.this.properties);
configurer.setIgnoreUnresolvablePlaceholders(true);
addBeanFactoryPostProcessor(configurer);
// Add all the post processors of the parent, e.g. to make sure system placeholders get expanded properly
for (Object postProcessor : getParent().getBeansOfType(BeanFactoryPostProcessor.class).values())
{
addBeanFactoryPostProcessor((BeanFactoryPostProcessor) postProcessor);
}
setClassLoader(ChildApplicationContextFactory.this.getParent().getClassLoader());
}
/*
* (non-Javadoc)
* @see org.springframework.context.support.AbstractApplicationContext#getResourcePatternResolver()
*/
@Override
protected ResourcePatternResolver getResourcePatternResolver()
{
// Ensure we can resolve resourced on JBoss 5
return new JBossEnabledResourcePatternResolver(this);
}
/*
* (non-Javadoc)
* @see
* org.springframework.context.support.AbstractApplicationContext#postProcessBeanFactory(org.springframework
* .beans.factory.config.ConfigurableListableBeanFactory)
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
{
// Propagate composite properties to list factories of the corresponding name
beanFactory.addBeanPostProcessor(new BeanPostProcessor()
{
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
{
if (bean instanceof ListFactoryBean
&& ChildApplicationContextFactory.this.compositePropertyTypes.containsKey(beanName))
{
Map<String, CompositeDataBean> beans = ChildApplicationContextFactory.this.compositeProperties
.get(beanName);
List<Object> beanList;
if (beans != null)
{
beanList = new ArrayList<Object>(beans.size());
for (CompositeDataBean wrapped : beans.values())
{
beanList.add(wrapped.getBean());
}
}
else
{
beanList = Collections.emptyList();
}
((ListFactoryBean) bean).setSourceList(beanList);
}
return bean;
}
});
}
}
}

View File

@@ -0,0 +1,247 @@
/*
* Copyright (C) 2005-2009 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.management.subsystems;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
/**
* A class that wraps an instance of a Java Bean class declared as a composite property type on
* {@link ChildApplicationContextFactory} making it configurable, either through alfresco-global.properties or a JMX
* console.
*
* @see ChildApplicationContextFactory
* @author dward
*/
public class CompositeDataBean extends AbstractPropertyBackedBean
{
/** The owning bean */
private final PropertyBackedBean owner;
/** The Java bean instance. */
private final Object bean;
/** A Spring wrapper around the Java bean, allowing easy configuration of properties. */
private final BeanWrapper wrappedBean;
/** The property names. */
private final Set<String> propertyNames;
/** The writeable properties. */
private final Set<String> writeableProperties;
/** The prefix used to look up default values for this bean's properties */
private String defaultKeyPrefix;
/** The prefix used to look up instance-specific default values for this bean's properties */
private String instanceKeyPrefix;
/**
* Constructor for dynamically created instances, e.g. through {@link ChildApplicationContextFactory}.
*
* @param parent
* the parent application context
* @param registry
* the registry of property backed beans
* @param propertyDefaults
* property defaults provided by the installer or System properties
* @param category
* the category
* @param id
* the instance id
* @param owner
* the owning bean
* @param type
* the class of Java bean to be wrapped
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public CompositeDataBean(ApplicationContext parent, PropertyBackedBean owner, PropertyBackedBeanRegistry registry,
Properties propertyDefaults, String category, Class<?> type, List<String> id) throws IOException
{
setApplicationContext(parent);
setRegistry(registry);
setPropertyDefaults(propertyDefaults);
setBeanName(category);
setId(id);
this.owner = owner;
try
{
this.bean = type.newInstance();
// Tell the bean its name if it cares
if (this.bean instanceof BeanNameAware)
{
((BeanNameAware) this.bean).setBeanName(id.get(id.size() - 1));
}
this.wrappedBean = new BeanWrapperImpl(this.bean);
PropertyDescriptor[] descriptors = this.wrappedBean.getPropertyDescriptors();
this.propertyNames = new TreeSet<String>();
this.writeableProperties = new TreeSet<String>();
for (PropertyDescriptor descriptor : descriptors)
{
Method readMethod = descriptor.getReadMethod();
if (readMethod != null)
{
if (readMethod.getDeclaringClass().isAssignableFrom(Object.class))
{
// Ignore Object properties such as class
continue;
}
this.propertyNames.add(descriptor.getName());
if (descriptor.getWriteMethod() != null)
{
this.writeableProperties.add(descriptor.getName());
}
}
}
afterPropertiesSet();
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception
{
// Derive a default and instance key prefix of the form "<parent>.default." and "<parent>.value.<this>."
StringBuilder defaultKeyPrefixBuff = new StringBuilder(200);
StringBuilder instanceKeyPrefixBuff = new StringBuilder(200);
List<String> id = getId();
int size = id.size();
if (size > 1)
{
defaultKeyPrefixBuff.append(id.get(size - 2)).append('.');
instanceKeyPrefixBuff.append(defaultKeyPrefixBuff);
}
defaultKeyPrefixBuff.append("default.");
instanceKeyPrefixBuff.append("value.").append(id.get(size - 1)).append('.');
this.defaultKeyPrefix = defaultKeyPrefixBuff.toString();
this.instanceKeyPrefix = instanceKeyPrefixBuff.toString();
// Set initial values according to property defaults.
super.afterPropertiesSet();
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#resolveDefault(java.lang.String)
*/
@Override
protected String resolveDefault(String name)
{
// Because we may have multiple instances, we try an instance-specific default before falling back to a general
// property level default
String value = super.resolveDefault(this.instanceKeyPrefix + name);
return value == null ? super.resolveDefault(this.defaultKeyPrefix + name) : value;
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String)
*/
public String getProperty(String name)
{
Object value = this.wrappedBean.getPropertyValue(name);
return value == null ? null : value.toString();
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames()
*/
public Set<String> getPropertyNames()
{
return this.propertyNames;
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, java.lang.String)
*/
public void setProperty(String name, String value)
{
this.wrappedBean.setPropertyValue(name, value);
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#isUpdateable(java.lang.String)
*/
@Override
public boolean isUpdateable(String name)
{
return this.writeableProperties.contains(name);
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#start()
*/
public void start()
{
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#stop()
*/
public void stop()
{
// Ensure any edits to child composites cause the parent to be shut down and subsequently re-initialized
this.owner.stop();
}
/**
* Gets the wrapped Java bean.
*
* @return the Java bean
*/
protected Object getBean()
{
return this.bean;
}
}

View File

@@ -34,12 +34,7 @@ import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* A default {@link ChildApplicationContextManager} implementation that manages a 'chain' of
@@ -54,20 +49,17 @@ import org.springframework.context.event.ContextRefreshedEvent;
* this property is editable at runtime via JMX. If a new &lt;id> is included in the list then a new
* {@link ChildApplicationContextFactory} will be brought into existence. Similarly, if one is removed from the list,
* then the corresponding instance will be destroyed. For Alfresco community edition, the chain is best configured
* through the {@link #setDefaultChain(String)} method via Spring configuration.
* through the {@link #setChain(String)} method via Spring configuration.
*
* @author dward
*/
public class DefaultChildApplicationContextManager extends AbstractPropertyBackedBean implements
ApplicationContextAware, ApplicationListener, ChildApplicationContextManager
ChildApplicationContextManager
{
/** The name of the special property that holds the ordering of child instance names. */
private static final String ORDER_PROPERTY = "chain";
/** The parent. */
private ApplicationContext parent;
/** The default type name. */
private String defaultTypeName;
@@ -80,25 +72,12 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
/** The child application contexts. */
private Map<String, ChildApplicationContextFactory> childApplicationContexts = new TreeMap<String, ChildApplicationContextFactory>();
/** The auto start. */
private boolean autoStart;
/**
* Instantiates a new default child application context manager.
*/
public DefaultChildApplicationContextManager()
{
setId("manager");
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.
* ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.parent = applicationContext;
setId(Collections.singletonList("manager"));
}
/**
@@ -127,18 +106,6 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
this.defaultChain = defaultChain;
}
/**
* Indicates whether all child application contexts should be started on startup of the parent application context.
*
* @param autoStart
* <code>true</code> if all child application contexts should be started on startup of the parent
* application context
*/
public void setAutoStart(boolean autoStart)
{
this.autoStart = autoStart;
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#afterPropertiesSet()
@@ -151,7 +118,7 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
// Use the first type as the default, unless one is specified explicitly
if (this.defaultTypeName == null)
{
updateOrder(this.defaultChain, AbstractPropertyBackedBean.DEFAULT_ID);
updateOrder(this.defaultChain, AbstractPropertyBackedBean.DEFAULT_ID_ROOT);
this.defaultTypeName = this.childApplicationContexts.get(this.instanceIds.get(0)).getTypeName();
}
else
@@ -161,7 +128,7 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
}
else if (this.defaultTypeName == null)
{
setDefaultTypeName(AbstractPropertyBackedBean.DEFAULT_ID);
setDefaultTypeName(AbstractPropertyBackedBean.DEFAULT_ID_ROOT);
}
super.afterPropertiesSet();
@@ -173,7 +140,10 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
*/
public void start()
{
// Nothing to do
for (String instance : getInstanceIds())
{
getApplicationContext(instance);
}
}
/*
@@ -224,6 +194,16 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
return Collections.singleton(DefaultChildApplicationContextManager.ORDER_PROPERTY);
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.AbstractPropertyBackedBean#getDescription(java.lang.String)
*/
@Override
public String getDescription(String name)
{
return "Comma separated list of name:type pairs";
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, java.lang.String)
@@ -256,22 +236,6 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
return child == null ? null : child.getApplicationContext();
}
/*
* (non-Javadoc)
* @see
* org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
public void onApplicationEvent(ApplicationEvent event)
{
if (this.autoStart && event instanceof ContextRefreshedEvent && event.getSource() == this.parent)
{
for (String instance : getInstanceIds())
{
getApplicationContext(instance);
}
}
}
/**
* Gets the order string.
*
@@ -325,8 +289,12 @@ public class DefaultChildApplicationContextManager extends AbstractPropertyBacke
}
if (factory == null)
{
this.childApplicationContexts.put(id, new ChildApplicationContextFactory(this.parent,
getRegistry(), getCategory(), typeName, "managed$" + id));
// Generate a unique ID within the category
List<String> childId = new ArrayList<String>(2);
childId.add("managed");
childId.add(id);
this.childApplicationContexts.put(id, new ChildApplicationContextFactory(getParent(),
getRegistry(), getPropertyDefaults(), getCategory(), typeName, childId));
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2005-2009 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.management.subsystems;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.ManagedList;
/**
* A {@link BeanFactoryPostProcessor} that upgrades old-style Spring overrides that add location paths to the
* <code>repository-properties</code> or <code>hibernateConfigProperties</code> beans to instead add these paths to the
* <code>global-properties</code> bean. To avoid the warning messages output by this class, new property overrides
* should be added to alfresco-global.properties without overriding any bean definitions.
*
* @author dward
*/
public class LegacyConfigPostProcessor implements BeanFactoryPostProcessor
{
/** The name of the bean that, in new configurations, holds all properties */
private static final String BEAN_NAME_GLOBAL_PROPERTIES = "global-properties";
/** The name of the bean that expands repository properties. These should now be defaulted from global-properties. */
private static final String BEAN_NAME_REPOSITORY_PROPERTIES = "repository-properties";
/** The name of the bean that holds hibernate properties. These should now be overriden by global-properties. */
private static final String BEAN_NAME_HIBERNATE_PROPERTIES = "hibernateConfigProperties";
/** The name of the property on a Spring property loader that holds a list of property file location paths. */
private static final String PROPERTY_LOCATIONS = "locations";
/** The name of the property on a Spring property loader that holds a local property map. */
private static final String PROPERTY_PROPERTIES = "properties";
/** The logger. */
private static Log logger = LogFactory.getLog(LegacyConfigPostProcessor.class);
/*
* (non-Javadoc)
* @see
* org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.
* beans.factory.config.ConfigurableListableBeanFactory)
*/
@SuppressWarnings("unchecked")
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
try
{
// Look up the global-properties bean and its locations list
MutablePropertyValues globalProperties = beanFactory.getBeanDefinition(
LegacyConfigPostProcessor.BEAN_NAME_GLOBAL_PROPERTIES).getPropertyValues();
PropertyValue pv = globalProperties.getPropertyValue(LegacyConfigPostProcessor.PROPERTY_LOCATIONS);
Collection<Object> globalPropertyLocations;
Object value;
// Use the locations list if there is one, otherwise associate a new empty list
if (pv != null && (value = pv.getValue()) != null && value instanceof Collection)
{
globalPropertyLocations = (Collection<Object>) value;
}
else
{
globalPropertyLocations = new ManagedList(10);
globalProperties
.addPropertyValue(LegacyConfigPostProcessor.PROPERTY_LOCATIONS, globalPropertyLocations);
}
// Move location paths added to repository-properties
MutablePropertyValues repositoryProperties = processLocations(beanFactory, globalPropertyLocations,
LegacyConfigPostProcessor.BEAN_NAME_REPOSITORY_PROPERTIES, new String[]
{
"classpath:alfresco/version.properties"
});
// Fix up additional properties to enforce correct order of precedence
repositoryProperties.addPropertyValue("ignoreUnresolvablePlaceholders", Boolean.TRUE);
repositoryProperties.addPropertyValue("localOverride", Boolean.FALSE);
repositoryProperties.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_NEVER");
// Move location paths added to hibernateConfigProperties
MutablePropertyValues hibernateProperties = processLocations(beanFactory, globalPropertyLocations,
LegacyConfigPostProcessor.BEAN_NAME_HIBERNATE_PROPERTIES, new String[]
{
"classpath:alfresco/domain/hibernate-cfg.properties"
});
// Fix up additional properties to enforce correct order of precedence
hibernateProperties.addPropertyValue("localOverride", Boolean.TRUE);
}
catch (NoSuchBeanDefinitionException e)
{
// Ignore and continue
}
}
/**
* Given a bean name (assumed to implement {@link org.springframework.core.io.support.PropertiesLoaderSupport})
* checks whether it already references the <code>global-properties</code> bean. If not, 'upgrades' the bean by
* appending all additional resources it mentions in its <code>locations</code> property to
* <code>globalPropertyLocations</code>, except for those resources mentioned in <code>newLocations</code>. A
* reference to <code>global-properties</code> will then be added and the resource list in
* <code>newLocations<code> will then become the new <code>locations</code> list for the bean.
*
* @param beanFactory
* the bean factory
* @param globalPropertyLocations
* the list of global property locations to be appended to
* @param beanName
* the bean name
* @param newLocations
* the new locations to be set on the bean
* @return the mutable property values
*/
@SuppressWarnings("unchecked")
private MutablePropertyValues processLocations(ConfigurableListableBeanFactory beanFactory,
Collection<Object> globalPropertyLocations, String beanName, String[] newLocations)
{
// Get the bean an check its existing properties value
MutablePropertyValues beanProperties = beanFactory.getBeanDefinition(beanName).getPropertyValues();
PropertyValue pv = beanProperties.getPropertyValue(LegacyConfigPostProcessor.PROPERTY_PROPERTIES);
Object value;
// If the properties value already references the global-properties bean, we have nothing else to do. Otherwise,
// we have to 'upgrade' the bean definition.
if (pv == null || (value = pv.getValue()) == null || !(value instanceof BeanReference)
|| ((BeanReference) value).getBeanName().equals(LegacyConfigPostProcessor.BEAN_NAME_GLOBAL_PROPERTIES))
{
// Convert the array of new locations to a managed list of type string values, so that it is
// compatible with a bean definition
Collection<Object> newLocationList = new ManagedList(newLocations.length);
if (newLocations != null && newLocations.length > 0)
{
for (String preserveLocation : newLocations)
{
newLocationList.add(new TypedStringValue(preserveLocation));
}
}
// If there is currently a locations list, process it
pv = beanProperties.getPropertyValue(LegacyConfigPostProcessor.PROPERTY_LOCATIONS);
if (pv != null && (value = pv.getValue()) != null && value instanceof Collection)
{
Collection<Object> locations = (Collection<Object>) value;
// Compute the set of locations that need to be added to globalPropertyLocations (preserving order) and
// warn about each
Set<Object> addedLocations = new LinkedHashSet<Object>(locations);
addedLocations.removeAll(globalPropertyLocations);
addedLocations.removeAll(newLocationList);
for (Object location : addedLocations)
{
LegacyConfigPostProcessor.logger.warn("Legacy configuration detected: adding "
+ (location instanceof TypedStringValue ? ((TypedStringValue) location).getValue()
: location.toString()) + " to global-properties definition");
globalPropertyLocations.add(location);
}
}
// Ensure the bean now references global-properties
beanProperties.addPropertyValue(LegacyConfigPostProcessor.PROPERTY_PROPERTIES, new RuntimeBeanReference(
LegacyConfigPostProcessor.BEAN_NAME_GLOBAL_PROPERTIES));
// Ensure the new location list is now set on the bean
if (newLocationList.size() > 0)
{
beanProperties.addPropertyValue(LegacyConfigPostProcessor.PROPERTY_LOCATIONS, newLocationList);
}
else
{
beanProperties.removePropertyValue(LegacyConfigPostProcessor.PROPERTY_LOCATIONS);
}
}
return beanProperties;
}
}

View File

@@ -24,6 +24,7 @@
*/
package org.alfresco.repo.management.subsystems;
import java.util.List;
import java.util.Set;
/**
@@ -38,6 +39,7 @@ import java.util.Set;
*/
public interface PropertyBackedBean
{
/**
* Gets a human readable categorization of this bean, explaining its purpose. This category may be used e.g. in
* administration UIs and JMX object names.
@@ -47,11 +49,12 @@ public interface PropertyBackedBean
public String getCategory();
/**
* Gets an identifier for the bean. Must be unique within the category.
* Gets an identifier for the bean. Must be unique within the category. The ID is a List to encourage hierarchical
* structuring of IDs, e.g. to aid construction of JMX Object names and presentation in JConsole.
*
* @return the id
*/
public String getId();
public List<String> getId();
/**
* Gets the names of all properties.
@@ -89,6 +92,15 @@ public interface PropertyBackedBean
*/
public boolean isUpdateable(String name);
/**
* Gets a Human readable description of the property, e.g. to provide via JMX.
*
* @param name
* the name
* @return the description
*/
public String getDescription(String name);
/**
* Starts up the component, using its new property values.
*/

View File

@@ -25,19 +25,16 @@
package org.alfresco.repo.management.subsystems;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A configurable proxy for a set of {@link ApplicationContextFactory} beans that allows dynamic selection of one or
* more alternative subsystems via a <code>sourceBeanName</code> property. As with other {@link PropertyBackedBean}s,
* can be stopped, reconfigured, started and tested.
*/
public class SwitchableApplicationContextFactory extends AbstractPropertyBackedBean implements ApplicationContextAware,
public class SwitchableApplicationContextFactory extends AbstractPropertyBackedBean implements
ApplicationContextFactory
{
/**
@@ -45,9 +42,6 @@ public class SwitchableApplicationContextFactory extends AbstractPropertyBackedB
*/
private static final String SOURCE_BEAN_PROPERTY = "sourceBeanName";
/** The parent application context. */
private ApplicationContext parent;
/** The bean name of the source {@link ApplicationContextFactory}. */
private String sourceBeanName;
@@ -76,23 +70,17 @@ public class SwitchableApplicationContextFactory extends AbstractPropertyBackedB
}
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.
* ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.parent = applicationContext;
}
/*
* (non-Javadoc)
* @see org.alfresco.enterprise.repo.management.ConfigurableBean#onStart()
*/
public synchronized void start()
{
this.sourceApplicationContextFactory = (ApplicationContextFactory) this.parent.getBean(this.sourceBeanName);
if (this.sourceApplicationContextFactory == null)
{
this.sourceApplicationContextFactory = (ApplicationContextFactory) getParent().getBean(this.sourceBeanName);
this.sourceApplicationContextFactory.start();
}
}
/*
@@ -128,7 +116,8 @@ public class SwitchableApplicationContextFactory extends AbstractPropertyBackedB
return this.sourceApplicationContextFactory.getApplicationContext();
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getProperty(java.lang.String)
*/
public synchronized String getProperty(String name)
@@ -140,7 +129,8 @@ public class SwitchableApplicationContextFactory extends AbstractPropertyBackedB
return this.sourceBeanName;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#getPropertyNames()
*/
public Set<String> getPropertyNames()
@@ -148,7 +138,8 @@ public class SwitchableApplicationContextFactory extends AbstractPropertyBackedB
return Collections.singleton(SOURCE_BEAN_PROPERTY);
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.PropertyBackedBean#setProperty(java.lang.String, java.lang.String)
*/
public synchronized void setProperty(String name, String value)
@@ -157,9 +148,9 @@ public class SwitchableApplicationContextFactory extends AbstractPropertyBackedB
{
throw new IllegalStateException("Illegal attempt to write to property \"" + name + "\"");
}
if (!parent.containsBean(value))
if (!getParent().containsBean(value))
{
throw new IllegalStateException("\"" + value + "\" is not a valid bean name");
throw new IllegalStateException("\"" + value + "\" is not a valid bean name");
}
setSourceBeanName(value);
}