* cleaned up folder structure

* OSGiyfied artifact names
* polished poms a little
* edited Eclipse project names to align artifact id

git-svn-id: svn+ssh://svn.synyx.de/var/svn/synyx/opensource/hera/trunk@6618 5a64d73e-33d6-4ccc-9058-23f8668ecac9
This commit is contained in:
Oliver Gierke
2009-08-05 17:15:56 +00:00
parent 0aabad48fa
commit a0b7b1c2ca
91 changed files with 450 additions and 17 deletions

314
core/src/doc/core.xml Normal file
View File

@@ -0,0 +1,314 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter>
<title>Core</title>
<section>
<title>Introduction</title>
<para>Host system provides a plugin interface providers have to implement.
Core system is build to hold a container of instances of this interface
and works with them.</para>
<example>
<title>Basic example of plugin interface and host</title>
<programlisting language="java">/**
* Interface contract for the providers to be implemented.
*/
public interface MyPluginInterface {
public void bar();
}
/**
* A host application class working with instances of the plugin
* interface.
*/
public class HostImpl implements Host {
private List&lt;MyPluginInterface&gt; plugins;
/**
* Setter to inject the plugins
*/
public void setPlugins(List&lt;MyPluginInterface&gt; plugins) {
this.plugins = plugins;
}
/**
* Some business method actually working with the given plugins.
*/
public void someBusinessMethod() {
for (MyPluginInterface plugin : plugins) {
plugin.bar();
}
}
}</programlisting>
</example>
<para>This is the way you would typically construct a host component in
general. Leveraging dependency injection via setters allows flexible usage
in a variety of environments. Thus you could easily provide a factory
class that is able to lookup
<interfacename>MyPluginInterface</interfacename> implementations from the
classpath, instantiate them and inject them into HostImpl.</para>
<para>Using Spring as component container you could configure something
like this:</para>
<example>
<title>Configuring HostImpl with Spring</title>
<programlisting language="xml">&lt;bean id="host" class="com.acme.HostImpl"&gt;
&lt;property name="plugins"&gt;
&lt;list&gt;
&lt;bean class="MyPluginImplementation" /&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/bean&gt;</programlisting>
</example>
<para>This is pretty much well known to Spring developers and let's us
face the wall that this is rather static. Everytime you want to add a new
plugin implementation instance you have to modify configuration of the
core. Let's see how we can get this dance a little more.</para>
</section>
<section id="core.collecting-beans">
<title>Collecting Spring beans dynamically</title>
<para>With the <classname>BeanListBeanFactoryPostProcessor</classname>
<productname>Hera</productname> provides a Spring container extension,
that allows to lookup beans of a given type in the current
<interfacename>ApplicationContext</interfacename> and register them as
list under a given name. Take a look at the configuration now:</para>
<example>
<title>Host and plugin configuration with Hera support</title>
<programlisting lang="" language="xml">&lt;import resource="classpath*:com/acme/**/plugins.xml" /&gt;
&lt;bean id="host" class="com.acme.HostImpl"&gt;
&lt;property name="plugins" ref="plugins" /&gt;
&lt;/bean&gt;
&lt;bean class="org.synyx.hera.plugin.support.BeanListBeanFactoryPostProcessor"&gt;
&lt;property name="lists"&gt;
&lt;map&gt;
&lt;entry key="plugins" value="org.acme.MyPluginInterface" /&gt;
&lt;/map&gt;
&lt;/property&gt;
&lt;/bean&gt;</programlisting>
<programlisting language="xml">&lt;!-- In a file called plugins.xml in the plugin project --&gt;
&lt;bean class="MyPluginimplementation" /&gt;</programlisting>
</example>
<para>You can see that we include a wildcarded configurationfile that
allows plugin projects to easily contribute plugin implementations by
declaring them as beans in configuration files matching the wildcarded
path. If you use Spring 2.5 component scanning you don't have to use the
import trick at all as Spring would detect the implementation
automatically as long as it is annotated with <code>@Component</code>,
<code>@Service</code> a.s.o.</para>
<para>The <classname>BeanListBeanFactoryPostProcessor</classname> in turn
allows registering a map of lists to be created, where the maps entry key
is the id under which the list will be registered and the entry's value is
the type to be looked up.</para>
<note>
<para>The design of the
<classname>BeanListBeanFactoryPostProcessor</classname> might seem a
little confusing at first (especially to set a map on a property named
lists). This is due to the posibility to register more than one list to
be looked up. We think about dropping this functionality for the sake of
simplicity in future versions.</para>
</note>
<simplesect>
<title>A whole lotta XML - namespace to help!</title>
<para>Actually this already serves a lot of requirements we listed in
<xref linkend="preface.context" />. Nevertheless the amount of XML to be
written is quite large. Furthermore it's rather not intuitive to
configure a bean id as key, and a type as value. We can heavily shrink
the XML required to a single line by providing a Spring namespace
boiling configuration down to this:</para>
<example>
<title>Host configuration using the plugin namespace</title>
<programlisting language="xml">&lt;import resource="classpath*:com/acme/**/plugins.xml" /&gt;
&lt;bean id="host" class="com.acme.HostImpl"&gt;
&lt;property name="plugins" ref="plugins" /&gt;
&lt;/bean&gt;
&lt;plugin:list id="plugins" class="org.acme.MyPluginInterface" /&gt;</programlisting>
</example>
<para>Suggested you have added the namespace XSD into Eclipse and
installed Spring IDE, you should get code completion on filling the
class attribute.</para>
</simplesect>
<simplesect>
<title>Using inner beans</title>
<note>
<para>This feature is only available for version 0.3 and above!</para>
</note>
<para>The listing above features an indirection for the
<code>plugin</code> bean definition. Defining the plugin list as top
level bean can have advantages: you easily could place all plugin lists
in a dedicated configuration file, presenting all application extension
points in one single place. Nevertheless you also might choose to define
the list directly in the property declaration:</para>
<example>
<title>Using internal bean definition</title>
<programlisting language="xml">&lt;import resource="classpath*:com/acme/**/plugins.xml" /&gt;
&lt;bean id="host" class="com.acme.HostImpl"&gt;
&lt;property name="plugins"&gt;
&lt;plugin:list class="org.acme.MyPluginInterface" /&gt;
&lt;/property&gt;
&lt;/bean&gt;</programlisting>
</example>
<para>This way you have a more compact configuration, paying the prica
of tangling all extention points though possibly various config
files.</para>
</simplesect>
</section>
<section>
<title>Plugin beans</title>
<para>Using plain interfaces and
<classname>BeanListBeanFactoryPostProcessor</classname> offers an easy way
to dynamically lookup beans in Spring environments. Nevertheless, very
often you face the situation that you want to have dedicated access to a
subset of all plugins, choose plugins by a given criteria or use a decent
default plugin or the like. Thus we need a basic infrastructure interface
for plugin interfaces to extend and a more sophisticated plugin
container.</para>
<simplesect>
<title>Plugin</title>
<para>Hera's central infrastructure interfacte is
<interfacename>Plugin&lt;S&gt;</interfacename>, where S defines the
delimiter type you want to let implementations decide on, whether they
shall be invoked or not. Thus the plugin implementation have to
implement <methodname>supports(S delimiter)</methodname> to come to the
decision. Consider the following example:</para>
<example>
<title>Usage of Plugin interface</title>
<programlisting language="java">public enum ProductType {
SOFTWARE, HARDWARE;
}
public interface ProductProcessor extends Plugin&lt;ProductType&gt; {
public void process(Product product);
}</programlisting>
</example>
<para>This design would allow plugin providers to implement
<methodname>supports(ProductType productType)</methodname> to decide
which product types they want to process and provide actual processing
logic in <methodname>process(Product product)</methodname>.</para>
</simplesect>
<simplesect id="core.plugin-registry">
<title>PluginRegistry</title>
<para>Using a <interfacename>List</interfacename> as plugin container as
well as the <interfacename>Plugin</interfacename> interface you can now
select plugins supporting the given delimiter. To not reimplement the
lookup logic for common cases Hera provides a
<classname>PluginRegistry&lt;T extends Plugin&lt;S&gt;,
S&gt;</classname> interface that provides sophisticated methods to
access certain plugins:</para>
<example>
<title>Usage of the PluginRegistry</title>
<programlisting language="java">PluginRegistry&lt;ProductProcessor, ProductType&gt; registry =
SimplePluginRegistry.create();
// Add plugin instances
registry.add(new FooImplementation());
// Returns the first plugin supporting SOFTWARE
registry.getPluginFor(ProductType.SOFTWARE);
// Returns the first plugin supporting SOFTWARE,
// or DefaultPlugin if none found
registry.getPluginFor(ProductType.SOFTWARE, new DefaultPlugin());
// Returns all plugins supporting HARDWARE,
// throwing the given exception if none found
registry.getPluginsFor(ProductType.HARDWARE, new MyException("Damn!");</programlisting>
</example>
</simplesect>
<simplesect>
<title>Configuration and namespace</title>
<para>Similar to the
<classname>BeanListBeanFactoryPostProcessor</classname> described in
<xref linkend="core.collecting-beans" /> Hera provides a
<classname>PluginRegistryBeanFactoryPostProcessor</classname> to
automatically lookup beans of a dedicated type to be aggregated in a
<classname>PluginRegistry</classname>. Note that the type has to be
assignable to <interfacename>Plugin</interfacename> to let the registry
work as expected.</para>
<para>Furthermore there is also an element in the namespace to shrink
down configuration XML:</para>
<example>
<title>Using the XML namespace to configure a registry</title>
<programlisting language="xml">&lt;plugin:registry id="plugins" class="com.acme.MyPluginInterface" /&gt;</programlisting>
</example>
</simplesect>
<simplesect>
<title>Ordering plugins</title>
<para>Declaring plugin beans sometimes it is necessary to preserve a
certain order of plugins. Suppose you have a plugin host that already
defines one plugin that shall always be executed after all plugins
declared by extensions. Actually the Spring container typically returnes
beans in the order they were declared, so that you could import you
wildcarded config files right before declaring the default plugin.
Unfortunately the order of the beans is not contracted to be preserved
for the Spring container. Thus we need a different solution.</para>
<para>Spring provides two ways to order beans. First, you can implement
<interfacename>Ordered</interfacename> interface and implement
<methodname>getOrder</methodname> to place a plugin at a certain point
in the list. Secondly you can user the <classname>@Order</classname>
annotation. For more information on ordering capabilities of Spring see
the <ulink url="???">section on this topic in the Spring reference
documentation</ulink>.</para>
<para>Using the Hera namespace you will get a
<interfacename>PluginRegistry</interfacename> instance that is capable
of preserving the order defined by the mentioned means. Using Hera
programatically use
<classname>OrderAwarePluginRegistry</classname>.</para>
</simplesect>
</section>
</chapter>

View File

@@ -0,0 +1,81 @@
package org.synyx.hera.core;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
/**
* {@link PluginRegistry} implementation that can handle {@link Plugin}s using
* the {@link Ordered} interface or {@link Order} annotation.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public class OrderAwarePluginRegistry<T extends Plugin<S>, S> extends
SimplePluginRegistry<T, S> {
@SuppressWarnings("unchecked")
private Comparator<T> comparator = new AnnotationAwareOrderComparator();
/**
* Creates a new {@link SimplePluginRegistry}.
*
* @param <T>
* @param <S>
* @return
*/
public static <S, T extends Plugin<S>> PluginRegistry<T, S> create() {
return new OrderAwarePluginRegistry<T, S>();
}
/**
* Creates a new {@link OrderAwarePluginRegistry} with the given plugins.
*
* @param <S>
* @param <T>
* @param plugins
* @return
*/
public static <S, T extends Plugin<S>> PluginRegistry<T, S> create(
List<T> plugins) {
PluginRegistry<T, S> registry = create();
registry.setPlugins(plugins);
return registry;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#setPlugins(java.util.List)
*/
@Override
public void setPlugins(List<? extends T> plugins) {
Collections.sort(plugins, comparator);
super.setPlugins(plugins);
}
/*
* (non-Javadoc)
*
* @see
* org.synyx.hera.core.PluginRegistry#addPlugin(org.synyx.hera.core.Plugin)
*/
@Override
public void addPlugin(T plugin) {
super.addPlugin(plugin);
Collections.sort(getPlugins(), comparator);
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.hera.core;
/**
* Central interface for plugins for the system. This interface is meant to be
* extended by concrete plugin interfaces. Its core responsibility is to define
* a delimiter type and a selection callback with the delimiter as parameter.
* The delimiter is some kind of decision object concrete plugin implementations
* can use to decide if they are capable to be executed.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public interface Plugin<S> {
/**
* Returns if a plugin should be invoked according to the given delimiter.
*
* @param delimiter
* @return if the plugin should be invoked
*/
boolean supports(S delimiter);
}

View File

@@ -0,0 +1,126 @@
package org.synyx.hera.core;
import java.util.List;
/**
* Registry for plugins. Allows sophisticated typesafe access to implementations
* of interfaces extending {link Plugin}.
*
* @param <T> the concrete plugin interface
* @param <S> the delimiter type
* @author Oliver Gierke - gierke@synyx.de
*/
public interface PluginRegistry<T extends Plugin<S>, S> extends Iterable<T> {
/**
* Register plugins.
*
* @param plugins the plugins to set
*/
void setPlugins(List<? extends T> plugins);
/**
* Adds a given plugin to the registry.
*
* @param plugin
*/
void addPlugin(T plugin);
/**
* Removes a given plugin from the registry.
*
* @param plugin
*/
boolean removePlugin(T plugin);
/**
* Returns the first plugin found for the given originating system. Thus,
* further configured plugins are ignored.
*
* @param originatingSystem
* @return a plugin for the given originating system or {@code null} if none
* found
*/
T getPluginFor(S delimiter);
/**
* Returns all plugins for the given delimiter.
*
* @param delimiter
* @return a list of plugins or an empty list if none found
*/
List<T> getPluginsFor(S delimiter);
/**
* Retrieves a required plugin from the registry or throw the given
* exception if none can be found. If more than one plugins are found the
* first one will be returned.
*
* @param <E> the exception type to be thrown in case no plugin can be found
* @param delimiter
* @param ex the exception to be thrown in case no plugin can be found
* @return a single plugin for the given delimiter
* @throws E if no plugin can be found for the given delimiter
*/
<E extends Exception> T getPluginFor(S delimiter, E ex) throws E;
/**
* Retrieves all plugins for the given delimiter or throws an exception if
* no plugin can be found.
*
* @param <E> the exception type to be thrown
* @param delimiter
* @param ex
* @return all plugins for the given delimiter
* @throws E if no plugin can be found
*/
<E extends Exception> List<T> getPluginsFor(S delimiter, E ex) throws E;
/**
* Returns the first {@link Plugin} supporting the given delimiter or the
* given plugin if none can be found.
*
* @param delimiter
* @param plugin
* @return a single {@link Plugin} supporting the given delimiter or the
* given {@link Plugin} if none found
*/
T getPluginFor(S delimiter, T plugin);
/**
* Returns all {@link Plugin}s supporting the given delimiter or the given
* plugins if none found.
*
* @param delimiter
* @param plugins
* @return all {@link Plugin}s supporting the given delimiter or the given
* {@link Plugin}s if none found
*/
List<? extends T> getPluginsFor(S delimiter, List<? extends T> plugins);
/**
* Returns the number of registered plugins.
*
* @return the number of plugins in the registry
*/
int countPlugins();
/**
* Returns whether the registry contains a given plugin.
*
* @param plugin
* @return
*/
boolean contains(T plugin);
}

View File

@@ -0,0 +1,260 @@
/*
* 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.hera.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Basic implementation of {@link PluginRegistry}. Simply holds all given
* plugins in a list.
*
* @param <T> the concrete plugin interface
* @param <S> the delimiter type
* @author Oliver Gierke - gierke@synyx.de
*/
public class SimplePluginRegistry<T extends Plugin<S>, S> implements
PluginRegistry<T, S> {
// Registered plugins
private List<T> plugins;
/**
* Creates a new {@code PluginRegistry}.
*/
public SimplePluginRegistry() {
plugins = new ArrayList<T>();
}
/**
* Creates a new {@link SimplePluginRegistry}.
*
* @param <T>
* @param <S>
* @return
*/
public static <S, T extends Plugin<S>> PluginRegistry<T, S> create() {
return new SimplePluginRegistry<T, S>();
}
/**
* Creates a new {@link SimplePluginRegistry}.
*
* @param <T>
* @param <S>
* @return
*/
public static <S, T extends Plugin<S>> PluginRegistry<T, S> create(
List<T> plugins) {
PluginRegistry<T, S> registry = create();
registry.setPlugins(plugins);
return registry;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#setPlugins(java.util.List)
*/
public void setPlugins(List<? extends T> plugins) {
this.plugins = new ArrayList<T>();
this.plugins.addAll(plugins);
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#addPlugin(T)
*/
public void addPlugin(T plugin) {
this.plugins.add(plugin);
}
/*
* (non-Javadoc)
*
* @see
* org.synyx.hera.core.PluginRegistry#removePlugin(org.synyx.hera.core.Plugin
* )
*/
public boolean removePlugin(T plugin) {
return this.plugins.remove(plugin);
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#getPluginFor(S)
*/
public T getPluginFor(S delimiter) {
List<T> plugins = getPluginsFor(delimiter);
if (0 < plugins.size()) {
return plugins.get(0);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#getPluginsFor(S)
*/
public List<T> getPluginsFor(S delimiter) {
List<T> result = new ArrayList<T>();
for (T plugin : plugins) {
if (plugin.supports(delimiter)) {
result.add(plugin);
}
}
return result;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#getPluginFor(S, E)
*/
public <E extends Exception> T getPluginFor(S delimiter, E ex) throws E {
T plugin = getPluginFor(delimiter);
if (null == plugin) {
throw ex;
}
return plugin;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#getPluginsFor(S, E)
*/
public <E extends Exception> List<T> getPluginsFor(S delimiter, E ex)
throws E {
List<T> plugins = getPluginsFor(delimiter);
if (0 == plugins.size()) {
throw ex;
}
return plugins;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#getPluginFor(S, T)
*/
public T getPluginFor(S delimiter, T plugin) {
T candidate = getPluginFor(delimiter);
return null == candidate ? plugin : candidate;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#getPluginsFor(S, java.util.List)
*/
public List<? extends T> getPluginsFor(S delimiter,
List<? extends T> plugins) {
List<T> candidates = getPluginsFor(delimiter);
return candidates.size() == 0 ? plugins : candidates;
}
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.PluginRegistry#countPlugins()
*/
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.
* <p>
* TODO: decide whether to make this method public
*
* @return all plugins of the registry
*/
protected List<? extends T> getPlugins() {
return plugins;
}
/*
* (non-Javadoc)
*
* @see
* org.synyx.hera.core.PluginRegistry#contains(org.synyx.hera.core.Plugin)
*/
public boolean contains(T plugin) {
return this.plugins.contains(plugin);
}
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
public Iterator<T> iterator() {
return plugins.iterator();
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.hera.core.config;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* Bean definition parser to register {@code <list />} elements from the plugin
* namespace.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public class PluginListDefinitionParser extends AbstractBeanDefinitionParser {
protected static final String PACKAGE = "org.synyx.hera.core.support.";
/**
* Returns the name of the {@link BeanFactoryPostProcessor} to be
* registered.
*
* @return
*/
protected String getPostProcessorName() {
return PACKAGE + "BeanListFactoryBean";
}
/*
* (non-Javadoc)
*
* @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) {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder
.genericBeanDefinition(getPostProcessorName());
builder.addPropertyValue("type", element.getAttribute("class"));
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;
}
/*
* (non-Javadoc)
*
* @seeorg.springframework.beans.factory.xml.AbstractBeanDefinitionParser#
* shouldGenerateIdAsFallback()
*/
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.hera.core.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* Simple namespace handler for {@literal plugin-config} namespace.
*
* @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());
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.hera.core.config;
/**
* Simple extension of {@link PluginListDefinitionParser}. Simply registers a
* {@code PluginRegistryBeanFactoryPostProcessor} instead of the original class.
*
* @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 getPostProcessorName() {
return PACKAGE + "PluginRegistryFactoryBean";
}
}

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
This package contains configuration support classes to ease registry configuration with
Spring namespaces.
</body>
</html>

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
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.
</body>
</html>

View File

@@ -0,0 +1,76 @@
/*
* 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.hera.core.support;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Abstract base class to implement types that need access to all beans of a
* given type from the {@link ApplicationContext}.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public abstract class AbstractTypeAwareSupport<T> implements
ApplicationContextAware {
private ApplicationContext context;
private Class<T> type;
/*
* (non-Javadoc)
*
* @see
* org.springframework.context.ApplicationContextAware#setApplicationContext
* (org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
/**
* @param type the type to set
*/
public void setType(Class<T> type) {
this.type = type;
}
/**
* Returns all beans from the {@link ApplicationContext} that match the
* given type.
*
* @return
*/
protected List<T> getBeans() {
@SuppressWarnings("unchecked")
Map<String, T> pluginMap = context.getBeansOfType(type);
return new ArrayList<T>(pluginMap.values());
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.hera.core.support;
import java.awt.List;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
/**
* Factory to create bean lists for a given type. Exposes all beans of the
* configured type that can be found in the {@link ApplicationContext}.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public class BeanListFactoryBean<T> extends AbstractTypeAwareSupport<T>
implements FactoryBean {
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public Object getObject() throws Exception {
return getBeans();
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@SuppressWarnings("unchecked")
public Class getObjectType() {
return List.class;
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.hera.core.support;
import org.springframework.beans.factory.FactoryBean;
import org.synyx.hera.core.OrderAwarePluginRegistry;
import org.synyx.hera.core.Plugin;
import org.synyx.hera.core.PluginRegistry;
/**
* {@link FactoryBean} to create {@link PluginRegistry} instances. Wraps a
* {@link BeanListFactoryBean}.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public class PluginRegistryFactoryBean<T extends Plugin<S>, S> extends
AbstractTypeAwareSupport<T> implements FactoryBean {
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public Object getObject() throws Exception {
return OrderAwarePluginRegistry.create(getBeans());
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@SuppressWarnings("unchecked")
public Class getObjectType() {
return PluginRegistry.class;
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
This package contains support classes to create bean lists or plugin
registry instances out of beans implementing a certain interface.
</body>
</html>

View File

@@ -0,0 +1 @@
http\://schemas.synyx.org/hera=org.synyx.hera.core.config.PluginNamespaceHandler

View File

@@ -0,0 +1 @@
http\://schemas.synyx.org/hera/hera.xsd=org/synyx/hera/core/config/hera.xsd

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://schemas.synyx.org/hera"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://schemas.synyx.org/hera"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:element name="list" type="pluginType">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:exports identifier="@id" type="java.util.List" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="registry" type="pluginType">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:exports identifier="@id" type="org.synyx.plugin.core.PluginRegistry" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="pluginType">
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="class" type="classType" />
<xsd:attribute name="init-factories" type="xsd:boolean" default="false" />
</xsd:complexType>
<xsd:simpleType name="classType">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,122 @@
package org.synyx.hera.core;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.annotation.Order;
/**
* Unit test for {@link OrderAwarePluginRegistry} that especially concentrates
* on testing ordering functionality.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public class OrderAwarePluginRegistryUnitTest extends
SimplePluginRegistryUnitTest {
private PluginRegistry<TestPlugin, String> registry;
private TestPlugin firstPlugin;
private TestPlugin secondPlugin;
@Before
public void setUp() {
super.setUp();
registry = OrderAwarePluginRegistry.create();
firstPlugin = new FirstImplementation();
secondPlugin = new SecondImplementation();
}
/**
* Adds the plugin implementations in order of their names, expecting the
* registry to order them correctly.
*
* @throws Exception
*/
@Test
public void honorsOrderOnAddPlugins() throws Exception {
registry.setPlugins(Arrays.asList(firstPlugin, secondPlugin));
assertOrder();
}
@Test
public void assertsOrderOnAddingPlugins() throws Exception {
registry.setPlugins(Arrays.asList(firstPlugin));
registry.addPlugin(secondPlugin);
assertOrder();
}
private void assertOrder() {
List<TestPlugin> plugins = registry.getPluginsFor(null);
assertEquals(2, plugins.size());
assertEquals(secondPlugin, plugins.get(0));
assertEquals(firstPlugin, plugins.get(1));
assertEquals(secondPlugin, registry.getPluginFor(null));
}
/**
* Simple test interface.
*
* @author Oliver Gierke - gierke@synyx.de
*/
private static interface TestPlugin extends Plugin<String> {
}
/**
* Plugin implementation, that is orderd right AFTER the second one.
*
* @author Oliver Gierke - gierke@synyx.de
*/
@Order(5)
private static class FirstImplementation implements TestPlugin {
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.Plugin#supports(java.lang.Object)
*/
public boolean supports(String delimiter) {
return true;
}
}
/**
* Plugin implementation that is ordered BEFORE the first one.
*
* @author Oliver Gierke - gierke@synyx.de
*/
@Order(1)
private static class SecondImplementation implements TestPlugin {
/*
* (non-Javadoc)
*
* @see org.synyx.hera.core.Plugin#supports(java.lang.Object)
*/
public boolean supports(String delimiter) {
return true;
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.hera.core;
/**
* @author Oliver Gierke - gierke@synyx.de
*/
public interface SamplePlugin extends Plugin<String> {
void pluginMethod();
}

View File

@@ -0,0 +1,28 @@
package org.synyx.hera.core;
/**
* @author Oliver Gierke - gierke@synyx.de
*/
public class SamplePluginHost {
private PluginRegistry<SamplePlugin, String> registry =
SimplePluginRegistry.create();
/**
* @param registry the registry to set
*/
public void setRegistry(PluginRegistry<SamplePlugin, String> registry) {
this.registry = registry;
}
/**
* @return the registry
*/
public PluginRegistry<SamplePlugin, String> getRegistry() {
return registry;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.hera.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() {
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.hera.core;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
/**
* Unit test for {@link SimplePluginRegistry}.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public class SimplePluginRegistryUnitTest {
private SamplePlugin plugin;
private PluginRegistry<SamplePlugin, String> registry;
/**
* Initializes a {@code PluginRegistry} and equips it with an {@code
* EmailNotificationProvider}.
*/
@Before
public void setUp() {
plugin = new SamplePluginImplementation();
registry = SimplePluginRegistry.create(Arrays.asList(plugin));
}
/**
* Asserts that the registry contains the plugin it was initialized with.
*
* @throws Exception
*/
@Test
public void assertRegistryInitialized() throws Exception {
assertEquals(1, registry.countPlugins());
assertTrue(registry.contains(plugin));
}
/**
* Asserts asking for a plugin with the {@code PluginMetadata} provided by
* the {@link EmailNotificationProvider}.
*/
@Test
public void assertFindsEmailNotificationProvider() {
String metadata = "FOO";
List<SamplePlugin> plugins = registry.getPluginsFor(metadata);
assertNotNull(plugins);
assertEquals(1, plugins.size());
SamplePlugin provider = plugins.get(0);
assertTrue(provider instanceof SamplePluginImplementation);
}
/**
* Expects the given exception to be thrown if no {@link Plugin} found.
*/
@Test(expected = IllegalArgumentException.class)
public void throwsExceptionIfNoPluginFound() {
registry.getPluginFor("BAR", new IllegalArgumentException());
}
/**
* Expects the given exception to be thrown if no {@link Plugin}s found.
*/
@Test(expected = IllegalArgumentException.class)
public void throwsExceptionIfNoPluginsFound() {
registry.getPluginsFor("BAR", new IllegalArgumentException());
}
/**
* Expect the defualt plugin to be returned if none found.
*/
@Test
public void returnsDefaultIfNoneFound() {
SamplePlugin defaultPlugin = new SamplePluginImplementation();
assertEquals(defaultPlugin, registry.getPluginFor("BAR", defaultPlugin));
}
/**
* Expect the given default plugins to be returned if none found.
*/
@Test
public void returnsDefaultsIfNoneFound() {
List<? extends SamplePlugin> defaultPlugins =
Arrays.asList(new SamplePluginImplementation());
assertEquals(defaultPlugins, registry.getPluginsFor("BAR",
defaultPlugins));
}
}

View File

@@ -0,0 +1,57 @@
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;
/**
* Integration test to simply check if the configuration gets parsed correctly.
*
* @author Oliver Gierke - gierke@synyx.de
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application-context.xml")
public class PluginConfigurationIntegrationTest {
@Autowired
List<SamplePlugin> samplePlugins;
@Autowired
@Qualifier("bar")
PluginRegistry<SamplePlugin, String> pluginRegistry;
@Autowired
@Qualifier("host")
SamplePluginHost host;
@Autowired
@Qualifier("otherHost")
SamplePluginHost otherHost;
@Autowired
SamplePlugin plugin;
@Test
public void test() throws Exception {
assertNotNull(samplePlugins);
assertSame(pluginRegistry, host.getRegistry());
assertNotSame(pluginRegistry, otherHost.getRegistry());
assertTrue(samplePlugins.contains(plugin));
assertTrue(pluginRegistry.contains(plugin));
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:plugin="http://schemas.synyx.org/hera"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://schemas.synyx.org/hera http://schemas.synyx.org/hera/hera.xsd">
<plugin:list id="foo" class="org.synyx.hera.core.SamplePlugin" />
<plugin:registry id="bar" class="org.synyx.hera.core.SamplePlugin" />
<bean class="org.synyx.hera.core.SamplePluginImplementation" />
<bean id="host" class="org.synyx.hera.core.SamplePluginHost">
<property name="registry" ref="bar" />
</bean>
<bean id="otherHost" class="org.synyx.hera.core.SamplePluginHost">
<property name="registry">
<plugin:registry id="tadaa" class="org.synyx.hera.core.SamplePlugin" />
</property>
</bean>
</beans>

View File

@@ -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.hera=DEBUG