diff --git a/hera-core/src/main/java/org/synyx/hera/core/config/BeanReferenceBeanPostProcessor.java b/hera-core/src/main/java/org/synyx/hera/core/config/BeanReferenceBeanPostProcessor.java new file mode 100644 index 0000000..ca0ff53 --- /dev/null +++ b/hera-core/src/main/java/org/synyx/hera/core/config/BeanReferenceBeanPostProcessor.java @@ -0,0 +1,66 @@ +package org.synyx.hera.core.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.BeanReference; + + +/** + * {@link BeanPostProcessor} to handle {@link BeanReference} beans and replacing + * it by its reference target. + * + * @author Oliver Gierke - gierke@synyx.de + */ +public class BeanReferenceBeanPostProcessor implements BeanPostProcessor, + BeanFactoryAware { + + private BeanFactory beanFactory; + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org + * .springframework.beans.factory.BeanFactory) + */ + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + + this.beanFactory = beanFactory; + } + + + /* + * (non-Javadoc) + * + * @seeorg.springframework.beans.factory.config.BeanPostProcessor# + * postProcessAfterInitialization(java.lang.Object, java.lang.String) + */ + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + + // Skip non RuntimeBeanReferences + if (!(bean instanceof BeanReference)) { + return bean; + } + + BeanReference reference = (BeanReference) bean; + + return beanFactory.getBean(reference.getBeanName()); + } + + + /* + * (non-Javadoc) + * + * @seeorg.springframework.beans.factory.config.BeanPostProcessor# + * postProcessBeforeInitialization(java.lang.Object, java.lang.String) + */ + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + + return bean; + } +} \ No newline at end of file diff --git a/hera-core/src/main/java/org/synyx/hera/core/config/PluginListDefinitionParser.java b/hera-core/src/main/java/org/synyx/hera/core/config/PluginListDefinitionParser.java index 7fac33f..da6b8be 100644 --- a/hera-core/src/main/java/org/synyx/hera/core/config/PluginListDefinitionParser.java +++ b/hera-core/src/main/java/org/synyx/hera/core/config/PluginListDefinitionParser.java @@ -15,31 +15,38 @@ */ package org.synyx.hera.core.config; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.RuntimeBeanNameReference; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** + * Bean definition parser to register {@code } elements from the plugin + * namespace. + * * @author Oliver Gierke - gierke@synyx.de */ -public class PluginListDefinitionParser extends - AbstractSingleBeanDefinitionParser { +public class PluginListDefinitionParser extends AbstractBeanDefinitionParser { protected static final String PACKAGE = "org.synyx.hera.core.support."; - /* - * (non-Javadoc) + /** + * Returns the name of the {@link BeanFactoryPostProcessor} to be + * registered. * - * @see - * org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser - * #getBeanClassName(org.w3c.dom.Element) + * @return */ - @Override - protected String getBeanClassName(Element element) { + protected String getPostProcessorName() { return PACKAGE + "BeanListBeanFactoryPostProcessor"; } @@ -48,18 +55,116 @@ public class PluginListDefinitionParser extends /* * (non-Javadoc) * - * @see - * org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser - * #doParse(org.w3c.dom.Element, - * org.springframework.beans.factory.support.BeanDefinitionBuilder) + * @seeorg.springframework.beans.factory.xml.AbstractBeanDefinitionParser# + * parseInternal(org.w3c.dom.Element, + * org.springframework.beans.factory.xml.ParserContext) */ @Override + protected AbstractBeanDefinition parseInternal(Element element, + ParserContext context) { + + if (context.isNested()) { + return parseNested(element, context); + } else { + return parseStandalone(element, context); + } + } + + + /** + * Parses a nested {@code } or {@code } element. + * Registers the {@link BeanFactoryPostProcessor} as in standalone mode but + * returns a reference to the list or registry created by instead of the + * {@link BeanFactoryPostProcessor} itself. + * + * @param element + * @param context + * @return + */ + private AbstractBeanDefinition parseNested(Element element, + ParserContext context) { + + // Step 1 - Create reference wrapped in a bean definition + BeanDefinitionBuilder builder = + BeanDefinitionBuilder + .genericBeanDefinition(RuntimeBeanNameReference.class + .getName()); + + // Extract id attribute from element or generate custom one + String idAttribute = element.getAttribute("id"); + String listId = + StringUtils.hasText(idAttribute) ? idAttribute + : BeanDefinitionReaderUtils.generateBeanName(builder + .getBeanDefinition(), context.getRegistry(), + true); + + // Let reference point to the bean with the calculated id + builder.addConstructorArgValue(listId); + + // Step 2 - Register BeanPostProcessor to unwrap the reference + registerReferenceResolver(element, context); + + // Step 3 - Set the id to let the BeanFactoryPostProcessor (BFPP) + // register the list as reference target + element.setAttribute("id", listId); + + // Step 4 - Register BFPP ourselves + AbstractBeanDefinition definition = parseStandalone(element, context); + String beanFactoryxPostProcessorId = + resolveId(element, definition, context); + + BeanDefinitionHolder holder = + new BeanDefinitionHolder(definition, + beanFactoryxPostProcessorId); + registerBeanDefinition(holder, context.getRegistry()); + + // Step 5 - Return the reference to the list created by the BFPP + return getSourcedBeanDefinition(builder, element, context); + } + + + /** + * Registers a {@link BeanReferenceBeanPostProcessor} to automatically + * unwrap {@link BeanDefinition}s that contain a reference to other beans. + * + * @param element + * @param context + */ + private void registerReferenceResolver(Element element, + ParserContext context) { + + BeanDefinitionBuilder builder = + BeanDefinitionBuilder + .genericBeanDefinition(BeanReferenceBeanPostProcessor.class); + + BeanDefinition definition = + getSourcedBeanDefinition(builder, element, context); + + registerBeanDefinition(new BeanDefinitionHolder(definition, generateId( + definition, context)), context.getRegistry()); + } + + + /** + * Parses a standalone {@code } or {@code } element and + * registers a {@link BeanFactoryPostProcessor} to automatically register + * all beans of the type specified in {@code class} property under the given + * id. + * + * @param element + * @param context + * @return + */ @SuppressWarnings("unchecked") - protected void doParse(Element element, BeanDefinitionBuilder builder) { + private AbstractBeanDefinition parseStandalone(Element element, + ParserContext context) { ManagedMap map = new ManagedMap(); map.put(element.getAttribute("id"), element.getAttribute("class")); + BeanDefinitionBuilder builder = + BeanDefinitionBuilder + .genericBeanDefinition(getPostProcessorName()); builder.addPropertyValue("lists", map); String initFactories = element.getAttribute("init-factories"); @@ -67,6 +172,40 @@ public class PluginListDefinitionParser extends if (StringUtils.hasText(initFactories)) { builder.addPropertyValue("allowEagerInit", initFactories); } + + return getSourcedBeanDefinition(builder, element, context); + } + + + /** + * Returns the bean definition prepared by the builder and has connected it + * to the {@code source} object. + * + * @param builder + * @param source + * @param context + * @return + */ + private AbstractBeanDefinition getSourcedBeanDefinition( + BeanDefinitionBuilder builder, Object source, ParserContext context) { + + AbstractBeanDefinition definition = builder.getRawBeanDefinition(); + definition.setSource(context.extractSource(source)); + + return definition; + } + + + /** + * Generates a custom id for the given {@link BeanDefinition}. + * + * @param definition + * @param context + * @return + */ + private String generateId(BeanDefinition definition, ParserContext context) { + + return context.getReaderContext().generateBeanName(definition); } diff --git a/hera-core/src/main/java/org/synyx/hera/core/config/PluginRegistryDefinitionParser.java b/hera-core/src/main/java/org/synyx/hera/core/config/PluginRegistryDefinitionParser.java index bdc4340..b99510c 100644 --- a/hera-core/src/main/java/org/synyx/hera/core/config/PluginRegistryDefinitionParser.java +++ b/hera-core/src/main/java/org/synyx/hera/core/config/PluginRegistryDefinitionParser.java @@ -15,12 +15,9 @@ */ package org.synyx.hera.core.config; -import org.w3c.dom.Element; - - /** * Simple extension of {@link PluginListDefinitionParser}. Simply registers a - * {@code PluginListDefinitionParser} instead of the original class. + * {@code PluginRegistryBeanFactoryPostProcessor} instead of the original class. * * @author Oliver Gierke - gierke@synyx.de */ @@ -34,7 +31,7 @@ public class PluginRegistryDefinitionParser extends PluginListDefinitionParser { * #getBeanClassName(org.w3c.dom.Element) */ @Override - protected String getBeanClassName(Element element) { + protected String getPostProcessorName() { return PACKAGE + "PluginRegistryBeanFactoryPostProcessor"; } diff --git a/hera-core/src/test/java/org/synyx/hera/core/SamplePluginHost.java b/hera-core/src/test/java/org/synyx/hera/core/SamplePluginHost.java new file mode 100644 index 0000000..c85c2e2 --- /dev/null +++ b/hera-core/src/test/java/org/synyx/hera/core/SamplePluginHost.java @@ -0,0 +1,28 @@ +package org.synyx.hera.core; + +/** + * @author Oliver Gierke - gierke@synyx.de + */ +public class SamplePluginHost { + + private PluginRegistry registry = + PluginRegistry.create(); + + + /** + * @param registry the registry to set + */ + public void setRegistry(PluginRegistry registry) { + + this.registry = registry; + } + + + /** + * @return the registry + */ + public PluginRegistry getRegistry() { + + return registry; + } +} diff --git a/hera-core/src/test/java/org/synyx/hera/core/config/PluginConfigurationIntegrationTest.java b/hera-core/src/test/java/org/synyx/hera/core/config/PluginConfigurationIntegrationTest.java index ff095ea..5fb688d 100644 --- a/hera-core/src/test/java/org/synyx/hera/core/config/PluginConfigurationIntegrationTest.java +++ b/hera-core/src/test/java/org/synyx/hera/core/config/PluginConfigurationIntegrationTest.java @@ -1,14 +1,18 @@ package org.synyx.hera.core.config; +import static org.junit.Assert.*; + import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.synyx.hera.core.PluginRegistry; import org.synyx.hera.core.SamplePlugin; +import org.synyx.hera.core.SamplePluginHost; /** @@ -24,11 +28,22 @@ public class PluginConfigurationIntegrationTest { List samplePlugins; @Autowired + @Qualifier("bar") PluginRegistry pluginRegistry; + @Autowired + @Qualifier("host") + SamplePluginHost host; + + @Autowired + @Qualifier("otherHost") + SamplePluginHost otherHost; + @Test public void test() throws Exception { + assertSame(pluginRegistry, host.getRegistry()); + assertNotSame(pluginRegistry, otherHost.getRegistry()); } } diff --git a/hera-core/src/test/resources/application-context.xml b/hera-core/src/test/resources/application-context.xml index 85f634c..1b91a2c 100644 --- a/hera-core/src/test/resources/application-context.xml +++ b/hera-core/src/test/resources/application-context.xml @@ -9,6 +9,17 @@ - - + + + + + + + + + + + + + diff --git a/hera-core/src/test/resources/log4j.properties b/hera-core/src/test/resources/log4j.properties index eb2c175..3774e1f 100644 --- a/hera-core/src/test/resources/log4j.properties +++ b/hera-core/src/test/resources/log4j.properties @@ -9,4 +9,4 @@ log4j.rootLogger=WARN, stdout # Hibernate logging options (INFO only shows startup messages) log4j.logger.org.springframework=INFO -log4j.logger.org.synyx.plugin=DEBUG \ No newline at end of file +log4j.logger.org.synyx.hera=DEBUG \ No newline at end of file