improve extension binding and added api to query extensions

This commit is contained in:
Sebastian Sdorra
2014-12-19 17:15:50 +01:00
parent 0dcc94e2e1
commit f64f29bb2e
8 changed files with 697 additions and 229 deletions

View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Stopwatch;
import com.google.inject.Binder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Sebastian Sdorra
*/
public class DefaultExtensionProcessor implements ExtensionProcessor
{
/**
* the logger for DefaultExtensionProcessor
*/
private static final Logger logger =
LoggerFactory.getLogger(DefaultExtensionProcessor.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param collector
*/
public DefaultExtensionProcessor(ExtensionCollector collector)
{
this.collector = collector;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param extensionPoint
*
* @return
*/
@Override
public Iterable<Class> byExtensionPoint(Class extensionPoint)
{
return collector.byExtensionPoint(extensionPoint);
}
/**
* Method description
*
*
* @param extensionPoint
*
* @return
*/
@Override
public Class oneByExtensionPoint(Class extensionPoint)
{
return collector.oneByExtensionPoint(extensionPoint);
}
/**
* Method description
*
*
* @param binder
*/
@Override
public void processAutoBindExtensions(Binder binder)
{
logger.info("start processing extensions");
Stopwatch sw = Stopwatch.createStarted();
new ExtensionBinder(binder).bind(collector);
logger.info("bound extensions in {}", sw.stop());
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ExtensionCollector collector;
}

View File

@@ -39,8 +39,6 @@ import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.Binder;
import com.google.inject.Module;
import org.slf4j.Logger;
@@ -102,8 +100,8 @@ public class DefaultPluginLoader implements PluginLoader
modules = getInstalled(parent, context, PATH_MODULECONFIG);
appendExtensions(multiple, single, extensions, modules);
appendExtensions(multiple, single, extensions, unwrap());
collector = new ExtensionCollector(Iterables.concat(modules, unwrap()));
extensionProcessor = new DefaultExtensionProcessor(collector);
}
catch (IOException | JAXBException ex)
{
@@ -111,31 +109,20 @@ public class DefaultPluginLoader implements PluginLoader
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param binder
*/
@Override
public void processExtensions(Binder binder)
{
logger.info("start processing extensions");
if (logger.isInfoEnabled())
{
logger.info(
"found {} extensions for {} multiple and {} single extension points",
extensions.size(), multiple.size(), single.size());
}
new ExtensionBinder(binder).bind(multiple, single, extensions);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public ExtensionProcessor getExtensionProcessor()
{
return extensionProcessor;
}
/**
* Method description
*
@@ -145,7 +132,7 @@ public class DefaultPluginLoader implements PluginLoader
@Override
public Set<Module> getInjectionModules()
{
return ImmutableSet.copyOf(injectionModules);
return ImmutableSet.copyOf(collector.getInjectionModules());
}
/**
@@ -198,56 +185,6 @@ public class DefaultPluginLoader implements PluginLoader
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param multiple
* @param single
* @param extensions
* @param mods
*/
private void appendExtensions(Set<Class> multiple, Set<Class> single,
Set<Class> extensions, Iterable<? extends ScmModule> mods)
{
for (ScmModule mod : mods)
{
for (ExtensionPointElement epe : mod.getExtensionPoints())
{
if (epe.isMultiple())
{
multiple.add(epe.getClazz());
}
else
{
single.add(epe.getClazz());
}
}
for (Class extensionClass : mod.getExtensions())
{
if (Module.class.isAssignableFrom(extensionClass))
{
try
{
injectionModules.add((Module) extensionClass.newInstance());
}
catch (IllegalAccessException | InstantiationException ex)
{
logger.error("could not create instance of module", ex);
}
}
else
{
extensions.add(extensionClass);
}
}
Iterables.addAll(extensions, mod.getRestProviders());
Iterables.addAll(extensions, mod.getRestResources());
}
}
/**
* Method description
*
@@ -296,27 +233,21 @@ public class DefaultPluginLoader implements PluginLoader
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ExtensionCollector collector;
/** Field description */
private final ExtensionProcessor extensionProcessor;
/** Field description */
private final Set<PluginWrapper> installedPlugins;
/** Field description */
private final Set<ScmModule> modules;
/** Field description */
private final ClassLoader uberClassLoader;
/** Field description */
private final UberWebResourceLoader uberWebResourceLoader;
/** Field description */
private Set<PluginWrapper> installedPlugins;
/** Field description */
private Set<ScmModule> modules;
/** Field description */
private Set<Class> multiple = Sets.newHashSet();
/** Field description */
private Set<Class> single = Sets.newHashSet();
/** Field description */
private Set<Module> injectionModules = Sets.newHashSet();
/** Field description */
private Set<Class> extensions = Sets.newHashSet();
}

View File

@@ -35,10 +35,8 @@ package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.Binder;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.multibindings.Multibinder;
@@ -49,12 +47,6 @@ import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.util.Set;
import javax.ws.rs.ext.Provider;
/**
*
* @author Sebastian Sdorra
@@ -63,6 +55,12 @@ import javax.ws.rs.ext.Provider;
public final class ExtensionBinder
{
/** Field description */
private static final String TYPE_LOOSE_EXT = "loose extension";
/** Field description */
private static final String TYPE_REST_RESOURCE = "rest resource";
/**
* the logger for ExtensionBinder
*/
@@ -88,93 +86,54 @@ public final class ExtensionBinder
* Method description
*
*
* @param mutipleExtensionPoints
* @param singleExtensionPoints
* @param extensions
* @param collector
*/
public void bind(Set<Class> mutipleExtensionPoints,
Set<Class> singleExtensionPoints, Set<Class> extensions)
public void bind(ExtensionCollector collector)
{
if (logger.isInfoEnabled())
logger.info("bind extensions to extension points");
for (ExtensionPointElement epe : collector.getExtensionPointElements())
{
logger.info("bind {} extensions to {} extension points",
extensions.size(),
mutipleExtensionPoints.size() + singleExtensionPoints.size());
}
Set<Class> boundClasses = Sets.newHashSet();
for (Class extensionPointClass : mutipleExtensionPoints)
{
bindMultiExtensionPoint(boundClasses, extensionPointClass, extensions);
}
for (Class extensionPointClass : singleExtensionPoints)
{
bindExtensionPoint(boundClasses, extensionPointClass, extensions);
}
Set<Class> extensionsCopy = Sets.newHashSet(extensions);
Iterables.removeAll(extensionsCopy, boundClasses);
for (Class extension : extensionsCopy)
{
AnnotatedBindingBuilder abb = binder.bind(extension);
if (isProvider(extension))
if (epe.isMultiple())
{
logger.info("bind provider {} as singleton", extension);
abb.in(Scopes.SINGLETON);
}
else if (isEagerSingleton(extension))
{
logger.info("bind {} as eager singleton, without extensionpoint",
extension);
abb.asEagerSingleton();
bindMultiExtensionPoint(epe, collector.byExtensionPoint(epe));
}
else
{
logger.info("bind {}, without extensionpoint", extension);
binder.bind(extension);
}
}
}
Class extension = collector.oneByExtensionPoint(epe);
/**
* Method description
*
*
*
* @param found
*
* @param boundClasses
* @param extensionPointClass
* @param extensions
*/
@SuppressWarnings("unchecked")
private void bindExtensionPoint(Set<Class> boundClasses,
Class extensionPointClass, Set<Class> extensions)
{
boolean bound = false;
for (Class extensionClass : extensions)
{
if (extensionPointClass.isAssignableFrom(extensionClass))
{
if (bound)
if (extension != null)
{
throw new IllegalStateException(
"extension point ".concat(extensionPointClass.getName()).concat(
" is not multiple and is already bound to another class"));
bindSingleInstance(epe, extension);
}
else
{
logger.warn("could not find extension for extension point {}",
epe.getClazz());
}
bindSingleInstance(extensionPointClass, extensionClass);
boundClasses.add(extensionClass);
bound = true;
}
}
logger.info("bind loose extensions");
bindLooseExtensions(collector.getLooseExtensions());
logger.info("bind rest providers");
bindRestProviders(collector.getRestProviders());
logger.info("bind rest resources");
bindRestResource(collector.getRestResources());
}
/**
* Method description
*
*
* @param extensions
*/
private void bindLooseExtensions(Iterable<Class> extensions)
{
for (Class extension : extensions)
{
singleBind(TYPE_LOOSE_EXT, extension);
}
}
/**
@@ -184,14 +143,17 @@ public final class ExtensionBinder
*
* @param found
*
* @param extensionPoint
*
* @param boundClasses
* @param extensionPointClass
* @param extensions
*/
@SuppressWarnings("unchecked")
private void bindMultiExtensionPoint(Set<Class> boundClasses,
Class extensionPointClass, Iterable<Class> extensions)
private void bindMultiExtensionPoint(ExtensionPointElement extensionPoint,
Iterable<Class> extensions)
{
Class extensionPointClass = extensionPoint.getClazz();
if (logger.isInfoEnabled())
{
logger.info("create multibinder for {}", extensionPointClass.getName());
@@ -202,47 +164,72 @@ public final class ExtensionBinder
for (Class extensionClass : extensions)
{
if (extensionPointClass.isAssignableFrom(extensionClass))
boolean eagerSingleton = isEagerSingleton(extensionClass);
if (logger.isInfoEnabled())
{
boolean eagerSingleton = isEagerSingleton(extensionClass);
if (logger.isInfoEnabled())
{
String as = Util.EMPTY_STRING;
if (eagerSingleton)
{
as = " as eager singleton";
}
logger.info("bind {} to multibinder of {}{}",
extensionClass.getName(), extensionPointClass.getName(), as);
}
ScopedBindingBuilder sbb = multibinder.addBinding().to(extensionClass);
String as = Util.EMPTY_STRING;
if (eagerSingleton)
{
sbb.asEagerSingleton();
logger.info("bind {} as eager singleton");
as = " as eager singleton";
}
boundClasses.add(extensionClass);
logger.info("bind {} to multibinder of {}{}", extensionClass.getName(),
extensionPointClass.getName(), as);
}
ScopedBindingBuilder sbb = multibinder.addBinding().to(extensionClass);
if (eagerSingleton)
{
sbb.asEagerSingleton();
}
}
}
/**
* Method description
*
*
* @param restProviders
*/
private void bindRestProviders(Iterable<Class> restProviders)
{
for (Class restProvider : restProviders)
{
logger.info("bind rest provider {}", restProvider);
binder.bind(restProvider).in(Singleton.class);
}
}
/**
* Method description
*
*
* @param restResources
*/
private void bindRestResource(Iterable<Class> restResources)
{
for (Class restResource : restResources)
{
singleBind(TYPE_REST_RESOURCE, restResource);
}
}
/**
* Method description
*
*
* @param extensionPointClass
*
* @param extensionPoint
* @param extensionClass
*/
@SuppressWarnings("unchecked")
private void bindSingleInstance(Class extensionPointClass,
private void bindSingleInstance(ExtensionPointElement extensionPoint,
Class extensionClass)
{
Class extensionPointClass = extensionPoint.getClazz();
boolean eagerSingleton = isEagerSingleton(extensionClass);
if (logger.isInfoEnabled())
@@ -267,6 +254,30 @@ public final class ExtensionBinder
}
}
/**
* Method description
*
*
* @param type
* @param extension
*/
private void singleBind(String type, Class extension)
{
StringBuilder log = new StringBuilder();
log.append("bind ").append(type).append(" ").append(extension);
AnnotatedBindingBuilder abb = binder.bind(extension);
if (isEagerSingleton(extension))
{
log.append(" as eager singleton");
abb.asEagerSingleton();
}
logger.info(log.toString());
}
//~--- get methods ----------------------------------------------------------
/**
@@ -282,19 +293,6 @@ public final class ExtensionBinder
return extensionClass.isAnnotationPresent(EagerSingleton.class);
}
/**
* Method description
*
*
* @param extensionClass
*
* @return
*/
private boolean isProvider(Class extensionClass)
{
return extensionClass.isAnnotationPresent(Provider.class);
}
//~--- fields ---------------------------------------------------------------
/** Field description */

View File

@@ -0,0 +1,333 @@
/**
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
*
* @author Sebastian Sdorra
*/
@SuppressWarnings("unchecked")
public final class ExtensionCollector
{
/**
* the logger for ExtensionCollector
*/
private static final Logger logger =
LoggerFactory.getLogger(ExtensionCollector.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param modules
*/
ExtensionCollector(Iterable<ScmModule> modules)
{
for (ScmModule module : modules)
{
collectRootElements(module);
}
for (ScmModule module : modules)
{
collectExtensions(module);
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param epe
*
* @return
*/
public Collection<Class> byExtensionPoint(ExtensionPointElement epe)
{
Collection<Class> collection = extensions.get(epe);
if ( collection == null ){
collection = Collections.EMPTY_SET;
}
return collection;
}
/**
* Method description
*
*
* @param clazz
*
* @return
*/
public Collection<Class> byExtensionPoint(Class clazz)
{
Collection<Class> exts;
ExtensionPointElement epe = extensionPointIndex.get(clazz);
if (epe != null)
{
exts = byExtensionPoint(epe);
}
else
{
exts = Collections.EMPTY_SET;
}
return exts;
}
/**
* Method description
*
*
* @param epe
*
* @return
*/
public Class oneByExtensionPoint(ExtensionPointElement epe)
{
return Iterables.getFirst(byExtensionPoint(epe), null);
}
/**
* Method description
*
*
* @param clazz
*
* @return
*/
public Class oneByExtensionPoint(Class clazz)
{
return Iterables.getFirst(byExtensionPoint(clazz), null);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Iterable<ExtensionPointElement> getExtensionPointElements()
{
return extensionPointIndex.values();
}
/**
* Method description
*
*
* @return
*/
public Multimap<ExtensionPointElement, Class> getExtensions()
{
return extensions;
}
/**
* Method description
*
*
* @return
*/
public Set<Module> getInjectionModules()
{
return injectionModules;
}
/**
* Method description
*
*
* @return
*/
public Set<Class> getLooseExtensions()
{
return looseExtensions;
}
/**
* Method description
*
*
* @return
*/
public Set<Class> getRestProviders()
{
return restProviders;
}
/**
* Method description
*
*
* @return
*/
public Set<Class> getRestResources()
{
return restResources;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param extension
*/
private void appendExtension(Class extension)
{
boolean found = false;
for (Entry<Class, ExtensionPointElement> e : extensionPointIndex.entrySet())
{
if (e.getKey().isAssignableFrom(extension))
{
extensions.put(e.getValue(), extension);
found = true;
break;
}
}
if (!found)
{
looseExtensions.add(extension);
}
}
/**
* Method description
*
*
* @param extension
*/
private void appendModule(Class extension)
{
try
{
injectionModules.add((Module) extension.newInstance());
}
catch (IllegalAccessException | InstantiationException ex)
{
logger.error("could not create instance of module", ex);
}
}
/**
* Method description
*
*
* @param module
*/
private void collectExtensions(ScmModule module)
{
for (Class extension : module.getExtensions())
{
if (Module.class.isAssignableFrom(extension))
{
appendModule(extension);
}
else
{
appendExtension(extension);
}
}
}
/**
* Method description
*
*
* @param module
*/
private void collectRootElements(ScmModule module)
{
for (ExtensionPointElement epe : module.getExtensionPoints())
{
extensionPointIndex.put(epe.getClazz(), epe);
}
restProviders.addAll(Lists.newArrayList(module.getRestProviders()));
restResources.addAll(Lists.newArrayList(module.getRestResources()));
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final Set<Class> looseExtensions = Sets.newHashSet();
/** Field description */
private final Set<Module> injectionModules = Sets.newHashSet();
/** Field description */
private final Set<Class> restProviders = Sets.newHashSet();
/** Field description */
private final Set<Class> restResources = Sets.newHashSet();
/** Field description */
private final Multimap<ExtensionPointElement, Class> extensions =
HashMultimap.create();
/** Field description */
private final Map<Class, ExtensionPointElement> extensionPointIndex =
Maps.newHashMap();
}