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