candidates = getPluginsFor(delimiter);
+
+ return candidates.size() == 0 ? plugins : candidates;
+ }
+
+
+ /**
+ * Returns the number of registered plugins.
+ *
+ * @return the number of plugins in the registry
+ */
+ public int countPlugins() {
+
+ return plugins.size();
+ }
+
+
+ /**
+ * Returns all registered plugins. Only use this method if you really need
+ * to access all plugins. For distinguished access to certain plugins favour
+ * accessor methods like {link #getPluginFor} over this one. This method
+ * should only be used for testing purposes to check registry configuration.
+ *
+ * TODO: decide whether to make this method public
+ *
+ * @return all plugins of the registry
+ */
+ @SuppressWarnings("unused")
+ private List extends T> getPlugins() {
+
+ return plugins;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Iterable#iterator()
+ */
+ public Iterator iterator() {
+
+ return plugins.iterator();
+ }
+}
diff --git a/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginListDefinitionParser.java b/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginListDefinitionParser.java
new file mode 100644
index 0000000..3250405
--- /dev/null
+++ b/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginListDefinitionParser.java
@@ -0,0 +1,47 @@
+package org.synyx.plugin.core.config;
+
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.ManagedMap;
+import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
+import org.w3c.dom.Element;
+
+
+/**
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+public class PluginListDefinitionParser extends
+ AbstractSingleBeanDefinitionParser {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
+ * #getBeanClassName(org.w3c.dom.Element)
+ */
+ @Override
+ protected String getBeanClassName(Element element) {
+
+ return "org.synyx.plugin.core.support.BeanListBeanFactoryPostProcessor";
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
+ * #doParse(org.w3c.dom.Element,
+ * org.springframework.beans.factory.support.BeanDefinitionBuilder)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void doParse(Element element, BeanDefinitionBuilder builder) {
+
+ ManagedMap map = new ManagedMap();
+ map.put(element.getAttribute("id"), element.getAttribute("class"));
+
+ builder.addPropertyValue("lists", map);
+ }
+
+}
diff --git a/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginNamespaceHandler.java b/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginNamespaceHandler.java
new file mode 100644
index 0000000..3329cf3
--- /dev/null
+++ b/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginNamespaceHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.synyx.plugin.core.config;
+
+import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
+
+
+/**
+ * Simple namespace handler for {@literal dao-config} namespace.
+ *
+ * @author Eberhard Wolff
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+public class PluginNamespaceHandler extends NamespaceHandlerSupport {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.beans.factory.xml.NamespaceHandler#init()
+ */
+ public void init() {
+
+ registerBeanDefinitionParser("list", new PluginListDefinitionParser());
+ registerBeanDefinitionParser("registry",
+ new PluginRegistryDefinitionParser());
+ }
+}
diff --git a/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginRegistryDefinitionParser.java b/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginRegistryDefinitionParser.java
new file mode 100644
index 0000000..7239748
--- /dev/null
+++ b/plugin-core/src/main/java/org/synyx/plugin/core/config/PluginRegistryDefinitionParser.java
@@ -0,0 +1,23 @@
+package org.synyx.plugin.core.config;
+
+import org.w3c.dom.Element;
+
+
+/**
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+public class PluginRegistryDefinitionParser extends PluginListDefinitionParser {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
+ * #getBeanClassName(org.w3c.dom.Element)
+ */
+ @Override
+ protected String getBeanClassName(Element element) {
+
+ return "org.synyx.plugin.core.support.PluginRegistryBeanFactoryPostProcessor";
+ }
+}
diff --git a/plugin-core/src/main/java/org/synyx/plugin/core/package.html b/plugin-core/src/main/java/org/synyx/plugin/core/package.html
new file mode 100644
index 0000000..40faeb5
--- /dev/null
+++ b/plugin-core/src/main/java/org/synyx/plugin/core/package.html
@@ -0,0 +1,8 @@
+
+
+
+This package contains the core plugin API. It allows other modules implementing
+components that extend functionality defined by a plugin interface. Plugin clients
+can be equipped with plugin implementations.
+
+
\ No newline at end of file
diff --git a/plugin-core/src/main/java/org/synyx/plugin/core/support/BeanListBeanFactoryPostProcessor.java b/plugin-core/src/main/java/org/synyx/plugin/core/support/BeanListBeanFactoryPostProcessor.java
new file mode 100644
index 0000000..3e0bb7e
--- /dev/null
+++ b/plugin-core/src/main/java/org/synyx/plugin/core/support/BeanListBeanFactoryPostProcessor.java
@@ -0,0 +1,237 @@
+package org.synyx.plugin.core.support;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.config.BeanDefinition;
+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.RuntimeBeanReference;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.util.StringUtils;
+import org.synyx.plugin.core.PluginRegistry;
+
+
+/**
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+public class BeanListBeanFactoryPostProcessor implements
+ BeanFactoryPostProcessor {
+
+ private static final Log log =
+ LogFactory.getLog(BeanListBeanFactoryPostProcessor.class);
+
+ private Map> lists = new HashMap>();
+
+
+ /**
+ * Setter to inject required plugin registry configuration. The map's keys
+ * will be used as bean ids for the resulting {@link PluginRegistry}
+ * instances. These registry instances will contain all beans having the
+ * given type.
+ *
+ * @param registryMap
+ */
+ public void setLists(Map> lists) {
+
+ this.lists = lists;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.springframework.beans.factory.config.BeanFactoryPostProcessor#
+ * postProcessBeanFactory
+ * (org.springframework.beans.factory.config.ConfigurableListableBeanFactory
+ * )
+ */
+ public void postProcessBeanFactory(
+ ConfigurableListableBeanFactory beanFactory) throws BeansException {
+
+ if (!(beanFactory instanceof BeanDefinitionRegistry)) {
+ throw new IllegalArgumentException(
+ "Given beanFactory is not a BeanDefinitionRegistry!");
+ }
+
+ BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
+
+ // Do for each required wrapper
+ for (String wrapperName : lists.keySet()) {
+
+ Class> requiredType = lists.get(wrapperName);
+
+ // Find implementations of the component type
+ List beans =
+ getBeanReferencesOfType(beanFactory, requiredType);
+
+ registerListBeanDefinition(wrapperName, requiredType, beans,
+ registry);
+ }
+ }
+
+
+ /**
+ * Registers a {@link ListFactoryBean} containing the given beans under the
+ * given id. Allows subclasses to modify or wrap the actually registered
+ * bean definition by calling
+ * {@link #wrapListBeanDefinition(BeanDefinition)} with the bean definition
+ * of the {Īlinl {@link ListFactoryBean}.
+ *
+ * @param id
+ * @param beans
+ * @param registry
+ */
+ protected void registerListBeanDefinition(String id, Class type,
+ List beans, BeanDefinitionRegistry registry) {
+
+ // Create ListFactory bean definition
+ BeanDefinitionBuilder listDefinitionBuilder =
+ BeanDefinitionBuilder.rootBeanDefinition(ListFactoryBean.class);
+
+ // Set the implementations as source for the wrapper
+ listDefinitionBuilder.addPropertyValue("sourceList", beans);
+
+ BeanDefinition beanDefinition =
+ wrapListBeanDefinition(getSourcedBeanDefinition(listDefinitionBuilder));
+
+ if (log.isDebugEnabled()) {
+ log.debug(String.format(
+ "Registering bean '%s' of type %s containing %s!", id,
+ beanDefinition.getBeanClassName(), beans.toString()));
+ }
+
+ // Register wrapper under defined name
+ registry.registerBeanDefinition(id, beanDefinition);
+ }
+
+
+ /**
+ * Template method to allow subclasses to wrap the list bean definition into
+ * another bean definition that will actually be registered. The default
+ * implementation simply returns the given bean definition.
+ *
+ * @param beanDefinition
+ * @return
+ */
+ protected BeanDefinition wrapListBeanDefinition(
+ BeanDefinition beanDefinition) {
+
+ return beanDefinition;
+ }
+
+
+ /**
+ * Returns a {@link BeanDefinitionBuilder}s {@link BeanDefinition} setting
+ * the current {@link PluginRegistryBeanFactoryPostProcessor} as source.
+ *
+ * @param builder
+ * @return
+ */
+ protected BeanDefinition getSourcedBeanDefinition(
+ BeanDefinitionBuilder builder) {
+
+ AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
+ beanDefinition.setSource(this);
+
+ return beanDefinition;
+ }
+
+
+ /**
+ * Returns bean references to beans with the given type.
+ *
+ * @param beanFactory
+ * @param type
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ protected List getBeanReferencesOfType(
+ ConfigurableListableBeanFactory beanFactory, Class> type) {
+
+ List beanNames = getBeanCandidates(beanFactory, type);
+
+ List beanReferences =
+ new ManagedList(beanNames.size());
+
+ for (String beanName : beanNames) {
+
+ RuntimeBeanReference reference = new RuntimeBeanReference(beanName);
+ reference.setSource(this);
+
+ beanReferences.add(reference);
+ }
+
+ return (List) beanReferences;
+ }
+
+
+ /**
+ * Looks up candidate beans for the given type from the {@link BeanFactory}.
+ * Does not use Spring type lookup facilities as this initializes
+ * {@link FactoryBean}s eagerly breaking possible registered
+ * {@link BeanPostProcessor}s.
+ *
+ * We traverse the entire {@link BeanFactory} hierarchy to lookup beans.
+ *
+ * @param beanFactory
+ * @param type
+ * @return
+ */
+ private List getBeanCandidates(
+ ConfigurableListableBeanFactory beanFactory, Class> type) {
+
+ List candidates = new ArrayList();
+
+ // None given or most top bean factory reached?
+ if (null == beanFactory) {
+ return candidates;
+ }
+
+ String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
+
+ for (String beanName : beanDefinitionNames) {
+
+ BeanDefinition beanDefinition =
+ beanFactory.getBeanDefinition(beanName);
+
+ // No bean class available at all
+ if (!StringUtils.hasText(beanDefinition.getBeanClassName())) {
+ continue;
+ }
+
+ try {
+ Class> beanClass =
+ Class.forName(beanDefinition.getBeanClassName());
+
+ if (type.isAssignableFrom(beanClass)) {
+ candidates.add(beanName);
+ }
+
+ } catch (ClassNotFoundException e) {
+ continue;
+ }
+ }
+
+ // Traverse parent bean factories
+ if (beanFactory.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) {
+ candidates.addAll(getBeanCandidates(
+ (ConfigurableListableBeanFactory) beanFactory
+ .getParentBeanFactory(), type));
+ }
+
+ return candidates;
+ }
+}
diff --git a/plugin-core/src/main/java/org/synyx/plugin/core/support/PluginRegistryBeanFactoryPostProcessor.java b/plugin-core/src/main/java/org/synyx/plugin/core/support/PluginRegistryBeanFactoryPostProcessor.java
new file mode 100644
index 0000000..030ae2e
--- /dev/null
+++ b/plugin-core/src/main/java/org/synyx/plugin/core/support/PluginRegistryBeanFactoryPostProcessor.java
@@ -0,0 +1,41 @@
+package org.synyx.plugin.core.support;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.synyx.plugin.core.PluginRegistry;
+
+
+/**
+ * This {@link BeanFactoryPostProcessor} automatically looksup bean instances
+ * from the {@link BeanFactory} hierarchy and registers {@link PluginRegistry}
+ * instances for them.
+ *
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+public class PluginRegistryBeanFactoryPostProcessor extends
+ BeanListBeanFactoryPostProcessor {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.synyx.minos.core.plugin.support.BeanListBeanFactoryPostProcessor#
+ * wrapBeanDefinition
+ * (org.springframework.beans.factory.config.BeanDefinition)
+ */
+ @Override
+ protected BeanDefinition wrapListBeanDefinition(
+ BeanDefinition beanDefinition) {
+
+ // Create PluginRegistry bean definition
+ BeanDefinitionBuilder builder =
+ BeanDefinitionBuilder.rootBeanDefinition(PluginRegistry.class);
+
+ // Set list definition
+ builder.addPropertyValue("plugins", beanDefinition);
+
+ return getSourcedBeanDefinition(builder);
+ }
+}
diff --git a/plugin-core/src/test/java/org/synyx/plugin/core/SamplePlugin.java b/plugin-core/src/test/java/org/synyx/plugin/core/SamplePlugin.java
new file mode 100644
index 0000000..c8c1651
--- /dev/null
+++ b/plugin-core/src/test/java/org/synyx/plugin/core/SamplePlugin.java
@@ -0,0 +1,9 @@
+package org.synyx.plugin.core;
+
+/**
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+public interface SamplePlugin extends Plugin {
+
+ void pluginMethod();
+}
diff --git a/plugin-core/src/test/java/org/synyx/plugin/core/SamplePluginImplementation.java b/plugin-core/src/test/java/org/synyx/plugin/core/SamplePluginImplementation.java
new file mode 100644
index 0000000..9debc7e
--- /dev/null
+++ b/plugin-core/src/test/java/org/synyx/plugin/core/SamplePluginImplementation.java
@@ -0,0 +1,27 @@
+package org.synyx.plugin.core;
+
+/**
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+public class SamplePluginImplementation implements SamplePlugin {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.synyx.plugin.core.Plugin#supports(java.lang.Object)
+ */
+ public boolean supports(String delimiter) {
+
+ return "FOO".equals(delimiter);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.synyx.plugin.core.ISamplePlugin#pluginMethod()
+ */
+ public void pluginMethod() {
+
+ }
+}
diff --git a/plugin-core/src/test/java/org/synyx/plugin/core/config/PluginConfigurationIntegrationTest.java b/plugin-core/src/test/java/org/synyx/plugin/core/config/PluginConfigurationIntegrationTest.java
new file mode 100644
index 0000000..4b68b2a
--- /dev/null
+++ b/plugin-core/src/test/java/org/synyx/plugin/core/config/PluginConfigurationIntegrationTest.java
@@ -0,0 +1,32 @@
+package org.synyx.plugin.core.config;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.synyx.plugin.core.PluginRegistry;
+import org.synyx.plugin.core.SamplePlugin;
+
+
+/**
+ * @author Oliver Gierke - gierke@synyx.de
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = "classpath:application-context.xml")
+public class PluginConfigurationIntegrationTest {
+
+ @Autowired
+ List samplePlugins;
+
+ @Autowired
+ PluginRegistry pluginRegistry;
+
+
+ @Test
+ public void test() throws Exception {
+
+ }
+}
diff --git a/plugin-core/src/test/resources/application-context.xml b/plugin-core/src/test/resources/application-context.xml
new file mode 100644
index 0000000..a7c13ae
--- /dev/null
+++ b/plugin-core/src/test/resources/application-context.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin-core/src/test/resources/log4j.properties b/plugin-core/src/test/resources/log4j.properties
new file mode 100644
index 0000000..eb2c175
--- /dev/null
+++ b/plugin-core/src/test/resources/log4j.properties
@@ -0,0 +1,12 @@
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
+
+# Root logger option
+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