From f64f29bb2e6f5c3b4ab0072cdf4f0c2d1e9b54a7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 19 Dec 2014 17:15:50 +0100 Subject: [PATCH] improve extension binding and added api to query extensions --- .../sonia/scm/plugin/ExtensionProcessor.java | 72 ++++ .../java/sonia/scm/plugin/PluginLoader.java | 12 +- .../java/sonia/scm/ScmContextListener.java | 23 +- .../main/java/sonia/scm/ScmServletModule.java | 2 +- .../scm/plugin/DefaultExtensionProcessor.java | 119 +++++++ .../sonia/scm/plugin/DefaultPluginLoader.java | 123 ++----- .../sonia/scm/plugin/ExtensionBinder.java | 242 +++++++------ .../sonia/scm/plugin/ExtensionCollector.java | 333 ++++++++++++++++++ 8 files changed, 697 insertions(+), 229 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java diff --git a/scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java b/scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java new file mode 100644 index 0000000000..90221db055 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java @@ -0,0 +1,72 @@ +/** + * 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.inject.Binder; + +/** + * Process and resolve extensions. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public interface ExtensionProcessor +{ + + /** + * Return extensions + * + * + * @param extensionPoint + * @return + */ + public Iterable byExtensionPoint(Class extensionPoint); + + /** + * Method description + * + * + * @param extensionPoint + * @return + */ + public Class oneByExtensionPoint(Class extensionPoint); + + /** + * Method description + * + * + * @param binder + */ + public void processAutoBindExtensions(Binder binder); +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java b/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java index 49671b803c..97f46f0f22 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java @@ -35,10 +35,8 @@ package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Binder; import com.google.inject.Module; - //~--- JDK imports ------------------------------------------------------------ import java.util.Collection; @@ -54,11 +52,9 @@ public interface PluginLoader * Method description * * - * @param binder + * @return */ - public void processExtensions(Binder binder); - - //~--- get methods ---------------------------------------------------------- + public ExtensionProcessor getExtensionProcessor(); /** * Method description @@ -90,6 +86,8 @@ public interface PluginLoader * * * @return uber classloader + * + * @since 2.0.0 */ public ClassLoader getUberClassLoader(); @@ -99,6 +97,8 @@ public interface PluginLoader * * * @return uber webresourceloader + * + * @since 2.0.0 */ public UberWebResourceLoader getUberWebResourceLoader(); } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index bcdb7d1549..f9fe12a09a 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -35,6 +35,7 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import com.google.inject.Guice; import com.google.inject.Injector; @@ -43,11 +44,13 @@ import com.google.inject.servlet.GuiceServletContextListener; import org.apache.shiro.guice.web.ShiroWebModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import sonia.scm.cache.CacheManager; import sonia.scm.group.GroupManager; import sonia.scm.plugin.DefaultPluginLoader; import sonia.scm.plugin.PluginWrapper; -import sonia.scm.repository.HealthCheckContextListener; import sonia.scm.repository.RepositoryManager; import sonia.scm.store.StoreFactory; import sonia.scm.upgrade.UpgradeManager; @@ -69,6 +72,14 @@ import javax.servlet.ServletContextEvent; public class ScmContextListener extends GuiceServletContextListener { + /** + * the logger for ScmContextListener + */ + private static final Logger logger = + LoggerFactory.getLogger(ScmContextListener.class); + + //~--- constructors --------------------------------------------------------- + /** * Constructs ... * @@ -155,8 +166,6 @@ public class ScmContextListener extends GuiceServletContextListener // init servlet context listeners globalInjector.getInstance(ServletContextListenerHolder.class) .contextInitialized(servletContextEvent); - globalInjector.getInstance(HealthCheckContextListener.class) - .contextInitialized(servletContextEvent); //J+ } } @@ -205,6 +214,7 @@ public class ScmContextListener extends GuiceServletContextListener */ private Injector getDefaultInjector(ServletContext servletCtx) { + Stopwatch sw = Stopwatch.createStarted(); DefaultPluginLoader pluginLoader = new DefaultPluginLoader(servletCtx, parent, plugins); @@ -223,7 +233,12 @@ public class ScmContextListener extends GuiceServletContextListener SCMContextProvider ctx = SCMContext.getContext(); - return Guice.createInjector(ctx.getStage().getInjectionStage(), moduleList); + Injector injector = + Guice.createInjector(ctx.getStage().getInjectionStage(), moduleList); + + logger.info("created injector in {}", sw.stop()); + + return injector; } /** diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 66a12cc1a9..885c3f3e1e 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -269,7 +269,7 @@ public class ScmServletModule extends JerseyServletModule bind(HealthCheckContextListener.class); // bind extensions - pluginLoader.processExtensions(binder()); + pluginLoader.getExtensionProcessor().processAutoBindExtensions(binder()); // bind security stuff bind(PermissionResolver.class, RepositoryPermissionResolver.class); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java new file mode 100644 index 0000000000..46f13bb1a7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java @@ -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 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; +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java index e4277dc1d0..304bab39c9 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java @@ -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 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 multiple, Set single, - Set extensions, Iterable 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 installedPlugins; + + /** Field description */ + private final Set modules; + /** Field description */ private final ClassLoader uberClassLoader; /** Field description */ private final UberWebResourceLoader uberWebResourceLoader; - - /** Field description */ - private Set installedPlugins; - - /** Field description */ - private Set modules; - - /** Field description */ - private Set multiple = Sets.newHashSet(); - - /** Field description */ - private Set single = Sets.newHashSet(); - - /** Field description */ - private Set injectionModules = Sets.newHashSet(); - - /** Field description */ - private Set extensions = Sets.newHashSet(); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java index 2f938f6f56..ebef86ec5a 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java @@ -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 mutipleExtensionPoints, - Set singleExtensionPoints, Set 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 boundClasses = Sets.newHashSet(); - - for (Class extensionPointClass : mutipleExtensionPoints) - { - bindMultiExtensionPoint(boundClasses, extensionPointClass, extensions); - } - - for (Class extensionPointClass : singleExtensionPoints) - { - bindExtensionPoint(boundClasses, extensionPointClass, extensions); - } - - Set 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 boundClasses, - Class extensionPointClass, Set 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 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 boundClasses, - Class extensionPointClass, Iterable extensions) + private void bindMultiExtensionPoint(ExtensionPointElement extensionPoint, + Iterable 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 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 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 */ diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java new file mode 100644 index 0000000000..5b0576367f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java @@ -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 modules) + { + for (ScmModule module : modules) + { + collectRootElements(module); + } + + for (ScmModule module : modules) + { + collectExtensions(module); + } + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param epe + * + * @return + */ + public Collection byExtensionPoint(ExtensionPointElement epe) + { + Collection collection = extensions.get(epe); + if ( collection == null ){ + collection = Collections.EMPTY_SET; + } + return collection; + } + + /** + * Method description + * + * + * @param clazz + * + * @return + */ + public Collection byExtensionPoint(Class clazz) + { + Collection 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 getExtensionPointElements() + { + return extensionPointIndex.values(); + } + + /** + * Method description + * + * + * @return + */ + public Multimap getExtensions() + { + return extensions; + } + + /** + * Method description + * + * + * @return + */ + public Set getInjectionModules() + { + return injectionModules; + } + + /** + * Method description + * + * + * @return + */ + public Set getLooseExtensions() + { + return looseExtensions; + } + + /** + * Method description + * + * + * @return + */ + public Set getRestProviders() + { + return restProviders; + } + + /** + * Method description + * + * + * @return + */ + public Set getRestResources() + { + return restResources; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param extension + */ + private void appendExtension(Class extension) + { + boolean found = false; + + for (Entry 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 looseExtensions = Sets.newHashSet(); + + /** Field description */ + private final Set injectionModules = Sets.newHashSet(); + + /** Field description */ + private final Set restProviders = Sets.newHashSet(); + + /** Field description */ + private final Set restResources = Sets.newHashSet(); + + /** Field description */ + private final Multimap extensions = + HashMultimap.create(); + + /** Field description */ + private final Map extensionPointIndex = + Maps.newHashMap(); +}