From b58bd8a5ea3db2e001417fef8aeed9e1a18a81c1 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Mon, 18 May 2009 15:34:46 +0000 Subject: [PATCH] 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 --- config/alfresco/core-services-context.xml | 59 ++- config/alfresco/hibernate-context.xml | 19 +- .../default/imagemagick-transform-context.xml | 10 +- .../default/imagemagick-transform.properties | 7 +- .../default/openoffice-transform-context.xml | 4 +- .../default/openoffice-transform.properties | 4 +- .../default/swf-transform-context.xml | 4 +- .../default/swf-transform.properties | 3 +- .../AbstractPropertyBackedBean.java | 195 +++++++- .../ChildApplicationContextFactory.java | 454 +++++++++++++----- .../subsystems/CompositeDataBean.java | 247 ++++++++++ ...DefaultChildApplicationContextManager.java | 82 +--- .../subsystems/LegacyConfigPostProcessor.java | 208 ++++++++ .../subsystems/PropertyBackedBean.java | 16 +- .../SwitchableApplicationContextFactory.java | 37 +- 15 files changed, 1098 insertions(+), 251 deletions(-) create mode 100644 source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java create mode 100644 source/java/org/alfresco/repo/management/subsystems/LegacyConfigPostProcessor.java diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 6a555db6ff..7fa7a866c7 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -12,24 +12,56 @@ - - - - - true - + + + + + classpath:alfresco/repository.properties - classpath:alfresco/version.properties classpath:alfresco/domain/transaction.properties + + classpath*:alfresco-global.properties SYSTEM_PROPERTIES_MODE_OVERRIDE + + + + hibernate.dialect + hibernate.query.substitutions + hibernate.jdbc.use_get_generated_keys + hibernate.default_schema + + + + + + + + + + classpath:alfresco/version.properties + + + + true + + + + + + + false + + + SYSTEM_PROPERTIES_MODE_NEVER + @@ -107,8 +139,15 @@ - - + + + + + + + + diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index 4f65ba988a..38d6e62609 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -4,21 +4,22 @@ - - - - hibernate.dialect - hibernate.query.substitutions - hibernate.jdbc.use_get_generated_keys - hibernate.default_schema - - + + classpath:alfresco/domain/hibernate-cfg.properties + + + + + + true + + diff --git a/config/alfresco/subsystems/thirdparty/default/imagemagick-transform-context.xml b/config/alfresco/subsystems/thirdparty/default/imagemagick-transform-context.xml index ec8e6237bf..c212ccca4b 100644 --- a/config/alfresco/subsystems/thirdparty/default/imagemagick-transform-context.xml +++ b/config/alfresco/subsystems/thirdparty/default/imagemagick-transform-context.xml @@ -12,7 +12,7 @@ - ${thirdparty.img.exe} + ${img.exe} ${source} SPLIT:${options} ${target} @@ -23,13 +23,13 @@ - ${thirdparty.img.root} + ${img.root} - ${thirdparty.img.dyn} + ${img.dyn} - ${thirdparty.img.dyn} + ${img.dyn} @@ -46,7 +46,7 @@ - ${thirdparty.img.exe} + ${img.exe} -version diff --git a/config/alfresco/subsystems/thirdparty/default/imagemagick-transform.properties b/config/alfresco/subsystems/thirdparty/default/imagemagick-transform.properties index 12c4b129dd..2eabb94b31 100644 --- a/config/alfresco/subsystems/thirdparty/default/imagemagick-transform.properties +++ b/config/alfresco/subsystems/thirdparty/default/imagemagick-transform.properties @@ -1,5 +1,4 @@ # External executable locations -# Pick these up from repository.properties for backward compatibility -thirdparty.img.root=${img.root} -thirdparty.img.dyn=${img.dyn} -thirdparty.img.exe=${img.exe} +img.root=./ImageMagick +img.dyn=${img.root}/lib +img.exe=${img.root}/bin/convert diff --git a/config/alfresco/subsystems/thirdparty/default/openoffice-transform-context.xml b/config/alfresco/subsystems/thirdparty/default/openoffice-transform-context.xml index 5823be7502..18d545cad7 100644 --- a/config/alfresco/subsystems/thirdparty/default/openoffice-transform-context.xml +++ b/config/alfresco/subsystems/thirdparty/default/openoffice-transform-context.xml @@ -18,9 +18,9 @@ - ${thirdparty.ooo.exe} + ${ooo.exe} -accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager - -env:UserInstallation=file:///${thirdparty.ooo.user} + -env:UserInstallation=file:///${ooo.user} -nologo -headless -nofirststartwizard diff --git a/config/alfresco/subsystems/thirdparty/default/openoffice-transform.properties b/config/alfresco/subsystems/thirdparty/default/openoffice-transform.properties index 39bfcef905..d69cbc59b4 100644 --- a/config/alfresco/subsystems/thirdparty/default/openoffice-transform.properties +++ b/config/alfresco/subsystems/thirdparty/default/openoffice-transform.properties @@ -1,4 +1,2 @@ # External executable locations -# Pick these up from repository.properties for backward compatibility -thirdparty.ooo.exe=${ooo.exe} -thirdparty.ooo.user=${ooo.user} +ooo.exe=soffice diff --git a/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml b/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml index 2f97958737..079ef42951 100644 --- a/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml +++ b/config/alfresco/subsystems/thirdparty/default/swf-transform-context.xml @@ -12,7 +12,7 @@ - ${thirdparty.swf.exe} -V + ${swf.exe} -V @@ -26,7 +26,7 @@ - ${thirdparty.swf.exe} -T ${flashVersion} ${source} -o ${target} + ${swf.exe} -T ${flashVersion} ${source} -o ${target} diff --git a/config/alfresco/subsystems/thirdparty/default/swf-transform.properties b/config/alfresco/subsystems/thirdparty/default/swf-transform.properties index 7986607d5f..2ce7410472 100644 --- a/config/alfresco/subsystems/thirdparty/default/swf-transform.properties +++ b/config/alfresco/subsystems/thirdparty/default/swf-transform.properties @@ -1,3 +1,2 @@ # External executable locations -# Pick these up from repository.properties for backward compatibility -thirdparty.swf.exe=${swf.exe} +swf.exe=./bin/pdf2swf diff --git a/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java b/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java index 3060baba36..1e968a61e3 100644 --- a/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java +++ b/source/java/org/alfresco/repo/management/subsystems/AbstractPropertyBackedBean.java @@ -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 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 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 id) { this.id = id; } + /** + * Indicates whether the bean should be started on startup of the parent application context. + * + * @param autoStart + * true 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 null 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 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()); + } + + } } diff --git a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java index 8e6adec635..cc2f0bd6cd 100644 --- a/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/management/subsystems/ChildApplicationContextFactory.java @@ -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: *
    *
  • alfresco/subsystems/<category>/<typeName>/*-context.xml
  • - *
  • alfresco/extension/subsystems/<category>/<typeName>/<id/*-context.xml
  • + *
  • alfresco/extension/subsystems/<category>/<typeName>/<id>/*-context.xml
  • *
* The child application context may use ${} 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: *
    *
  • alfresco/subsystems/<category>/<typeName>/*.properties
  • - *
  • alfresco/extension/subsystems/<category>/<typeName>/<id/*.properties
  • + *
  • alfresco/extension/subsystems/<category>/<typeName>/<id>/*.properties
  • *
* 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! + *

+ * 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 + * compositePropertyTypes 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. + *

+ * 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. + *

+ * imap.server.mountPoints=Repository_virtual,Repository_archive + *

+ * 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". + *

+ * To set a property on one of the instances, you append ".value.<instance name>.<bean property name>" to the + * parent property name. For example: + *

+ * imap.server.mountPoints.value.Repository_virtual.mode=virtual + *

+ * To specify a default value for a property on all instances of the bean, you append ".default.<bean property name>" + * to the parent property name. For example: + *

+ * imap.server.mountPoints.default.store=${spaces.store} + *

+ * Note that it's perfectly valid to use placeholders in property values that will be resolved from other global + * properties. + *

+ * 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: + * + *

+ * <bean id="imap.server.mountPoints" class="org.springframework.beans.factory.config.ListFactoryBean">
+ * <property name="sourceList">
+ * <!-- Whatever you declare in here will get replaced by the property value list -->
+ * </property>
+ * </bean>
+ * 
+ * + * 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> compositePropertyTypes = Collections.emptyMap(); + + /** The composite property values. */ + private Map> compositeProperties = new TreeMap>(); + /** * 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 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 - * true 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 + * beanName property automatically. + */ + public void setCompositePropertyTypes(Map> 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 result = new TreeSet(((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 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 propertyValues = this.compositeProperties.get(name); + if (propertyValues == null) + { + if (value == null) + { + return; + } + propertyValues = Collections.emptyMap(); + } + + try + { + Map newPropertyValues = new LinkedHashMap(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 childId = new ArrayList(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 idsToRemove = new TreeSet(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 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 beans = ChildApplicationContextFactory.this.compositeProperties + .get(beanName); + List beanList; + if (beans != null) + { + beanList = new ArrayList(beans.size()); + for (CompositeDataBean wrapped : beans.values()) + { + beanList.add(wrapped.getBean()); + } + } + else + { + beanList = Collections.emptyList(); + } + ((ListFactoryBean) bean).setSourceList(beanList); + } + return bean; + } + }); } } } diff --git a/source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java b/source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java new file mode 100644 index 0000000000..4d7d8ed2a7 --- /dev/null +++ b/source/java/org/alfresco/repo/management/subsystems/CompositeDataBean.java @@ -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 propertyNames; + + /** The writeable properties. */ + private final Set 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 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(); + this.writeableProperties = new TreeSet(); + 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 ".default." and ".value.." + StringBuilder defaultKeyPrefixBuff = new StringBuilder(200); + StringBuilder instanceKeyPrefixBuff = new StringBuilder(200); + List 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 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; + } +} diff --git a/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java b/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java index def9255de0..b6cc366a46 100644 --- a/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java +++ b/source/java/org/alfresco/repo/management/subsystems/DefaultChildApplicationContextManager.java @@ -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 <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 childApplicationContexts = new TreeMap(); - /** 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 - * true 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 childId = new ArrayList(2); + childId.add("managed"); + childId.add(id); + this.childApplicationContexts.put(id, new ChildApplicationContextFactory(getParent(), + getRegistry(), getPropertyDefaults(), getCategory(), typeName, childId)); } } diff --git a/source/java/org/alfresco/repo/management/subsystems/LegacyConfigPostProcessor.java b/source/java/org/alfresco/repo/management/subsystems/LegacyConfigPostProcessor.java new file mode 100644 index 0000000000..60752ecb57 --- /dev/null +++ b/source/java/org/alfresco/repo/management/subsystems/LegacyConfigPostProcessor.java @@ -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 + * repository-properties or hibernateConfigProperties beans to instead add these paths to the + * global-properties 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 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) 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 global-properties bean. If not, 'upgrades' the bean by + * appending all additional resources it mentions in its locations property to + * globalPropertyLocations, except for those resources mentioned in newLocations. A + * reference to global-properties will then be added and the resource list in + * newLocations will then become the new locations 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 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 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 locations = (Collection) value; + + // Compute the set of locations that need to be added to globalPropertyLocations (preserving order) and + // warn about each + Set addedLocations = new LinkedHashSet(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; + } +} diff --git a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java index 854b372da7..f384bad950 100644 --- a/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java +++ b/source/java/org/alfresco/repo/management/subsystems/PropertyBackedBean.java @@ -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 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. */ diff --git a/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java b/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java index 49197de975..1102338762 100644 --- a/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java +++ b/source/java/org/alfresco/repo/management/subsystems/SwitchableApplicationContextFactory.java @@ -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 sourceBeanName 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 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); }