From e58175162426addf141036f741e98e4a67ef6fef Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 18 Jan 2013 09:39:32 +0100 Subject: [PATCH] find and bind extension points automatically --- .../main/java/sonia/scm/io/FileSystem.java | 2 +- .../java/sonia/scm/plugin/ExtensionPoint.java | 7 +- .../sonia/scm/plugin/ext/AnnotatedClass.java | 63 +++ .../sonia/scm/BindingExtensionProcessor.java | 498 ------------------ .../main/java/sonia/scm/DecoratorBinder.java | 128 ----- .../java/sonia/scm/ScmContextListener.java | 13 +- .../main/java/sonia/scm/ScmServletModule.java | 27 +- .../sonia/scm/plugin/DefaultPluginLoader.java | 493 ++++++++++++----- .../plugin/ext/DefaultAnnotationScanner.java | 7 +- .../sonia/scm/plugin/ext/ExtensionBinder.java | 211 ++++++++ .../java/sonia/scm/plugin/ext/Extensions.java | 94 ++++ 11 files changed, 762 insertions(+), 781 deletions(-) delete mode 100644 scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/DecoratorBinder.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/ext/ExtensionBinder.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/ext/Extensions.java diff --git a/scm-core/src/main/java/sonia/scm/io/FileSystem.java b/scm-core/src/main/java/sonia/scm/io/FileSystem.java index 39c366a512..e5c4a4a3d4 100644 --- a/scm-core/src/main/java/sonia/scm/io/FileSystem.java +++ b/scm-core/src/main/java/sonia/scm/io/FileSystem.java @@ -46,7 +46,7 @@ import java.io.IOException; * * @author Sebastian Sdorra */ -@ExtensionPoint +@ExtensionPoint(multi=false) public interface FileSystem { diff --git a/scm-core/src/main/java/sonia/scm/plugin/ExtensionPoint.java b/scm-core/src/main/java/sonia/scm/plugin/ExtensionPoint.java index 2118449838..d85470ce19 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ExtensionPoint.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ExtensionPoint.java @@ -47,5 +47,8 @@ import java.lang.annotation.Target; */ @Documented @Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.CLASS) -public @interface ExtensionPoint {} +@Retention(RetentionPolicy.RUNTIME) +public @interface ExtensionPoint +{ + boolean multi() default true; +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotatedClass.java b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotatedClass.java index f1e45e45dd..4bdcf5be35 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotatedClass.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotatedClass.java @@ -30,8 +30,13 @@ */ + package sonia.scm.plugin.ext; +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Objects; + //~--- JDK imports ------------------------------------------------------------ import java.lang.annotation.Annotation; @@ -60,6 +65,64 @@ public class AnnotatedClass this.annotatedClass = annotatedClass; } + //~--- methods -------------------------------------------------------------- + + /** + * {@inheritDoc} + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final AnnotatedClass other = (AnnotatedClass) obj; + + return Objects.equal(annotation, other.annotation) + && Objects.equal(annotatedClass, other.annotatedClass); + } + + /** + * {@inheritDoc} + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(annotation, annotatedClass); + } + + /** + * {@inheritDoc} + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("annotation", annotation) + .add("annotatedClass", annotatedClass) + .toString(); + //J+ + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java b/scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java deleted file mode 100644 index 5de786d970..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java +++ /dev/null @@ -1,498 +0,0 @@ -/** - * Copyright (c) 2010, 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; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.collect.Sets; -import com.google.inject.Binder; -import com.google.inject.Module; -import com.google.inject.multibindings.Multibinder; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.group.GroupListener; -import sonia.scm.io.FileSystem; -import sonia.scm.plugin.ext.Extension; -import sonia.scm.plugin.ext.ExtensionProcessor; -import sonia.scm.repository.BlameLinePreProcessor; -import sonia.scm.repository.BlameLinePreProcessorFactory; -import sonia.scm.repository.ChangesetPreProcessor; -import sonia.scm.repository.ChangesetPreProcessorFactory; -import sonia.scm.repository.FileObjectPreProcessor; -import sonia.scm.repository.FileObjectPreProcessorFactory; -import sonia.scm.repository.RepositoryHandler; -import sonia.scm.repository.RepositoryHook; -import sonia.scm.repository.RepositoryListener; -import sonia.scm.repository.RepositoryRequestListener; -import sonia.scm.repository.spi.RepositoryServiceResolver; -import sonia.scm.resources.ResourceHandler; -import sonia.scm.security.EncryptionHandler; -import sonia.scm.user.UserListener; -import sonia.scm.web.security.AuthenticationHandler; -import sonia.scm.web.security.AuthenticationListener; -import sonia.scm.web.security.DefaultAuthenticationHandler; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.HashSet; -import java.util.Set; - -import javax.servlet.ServletContextListener; - -/** - * - * @author Sebastian Sdorra - */ -public class BindingExtensionProcessor implements ExtensionProcessor -{ - - /** the logger for BindingExtensionProcessor */ - private static final Logger logger = - LoggerFactory.getLogger(BindingExtensionProcessor.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - */ - public BindingExtensionProcessor() - { - this.moduleSet = Sets.newHashSet(); - this.extensions = Sets.newHashSet(); - this.decoratorFactories = Sets.newHashSet(); - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param binder - */ - public void bindDecorators(Binder binder) - { - DecoratorBinder decoratorBinder = new DecoratorBinder(binder); - - for (Class> c : decoratorFactories) - { - decoratorBinder.bindFactory(c); - } - } - - /** - * Method description - * - * - * @param binder - */ - @SuppressWarnings("unchecked") - public void bindExtensions(Binder binder) - { - Multibinder repositoryHandlers = - Multibinder.newSetBinder(binder, RepositoryHandler.class); - Multibinder authenticators = - Multibinder.newSetBinder(binder, AuthenticationHandler.class); - Multibinder resourceHandler = - Multibinder.newSetBinder(binder, ResourceHandler.class); - Multibinder repositoryHookBinder = - Multibinder.newSetBinder(binder, RepositoryHook.class); - - // changeset pre processor - Multibinder changesetPreProcessorBinder = - Multibinder.newSetBinder(binder, ChangesetPreProcessor.class); - Multibinder changesetPreProcessorFactoryBinder = - Multibinder.newSetBinder(binder, ChangesetPreProcessorFactory.class); - - // fileobject pre processor - Multibinder fileObjectPreProcessorBinder = - Multibinder.newSetBinder(binder, FileObjectPreProcessor.class); - Multibinder fileObjectPreProcessorFactoryBinder = - Multibinder.newSetBinder(binder, FileObjectPreProcessorFactory.class); - - // blameline pre processor - Multibinder blameLinePreProcessorBinder = - Multibinder.newSetBinder(binder, BlameLinePreProcessor.class); - Multibinder blameLinePreProcessorFactoryBinder = - Multibinder.newSetBinder(binder, BlameLinePreProcessorFactory.class); - - // repository service resolver - Multibinder repositoryServiceResolverBinder = - Multibinder.newSetBinder(binder, RepositoryServiceResolver.class); - - // listeners - Multibinder repositoryListenerBinder = - Multibinder.newSetBinder(binder, RepositoryListener.class); - Multibinder userListenerBinder = - Multibinder.newSetBinder(binder, UserListener.class); - Multibinder groupListenerBinder = - Multibinder.newSetBinder(binder, GroupListener.class); - Multibinder authenticationListenerBinder = - Multibinder.newSetBinder(binder, AuthenticationListener.class); - Multibinder repositoryRequestListenerBinder = - Multibinder.newSetBinder(binder, RepositoryRequestListener.class); - Multibinder servletContextListenerBinder = - Multibinder.newSetBinder(binder, ServletContextListener.class); - - authenticators.addBinding().to(DefaultAuthenticationHandler.class); - - for (Class extensionClass : extensions) - { - if (RepositoryHandler.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind RepositoryHandler {}", extensionClass.getName()); - } - - binder.bind(extensionClass); - repositoryHandlers.addBinding().to(extensionClass); - } - else if (EncryptionHandler.class.isAssignableFrom(extensionClass)) - { - bind(binder, EncryptionHandler.class, extensionClass); - } - else if (AuthenticationHandler.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind AuthenticationHandler {}", - extensionClass.getName()); - } - - binder.bind(extensionClass); - authenticators.addBinding().to(extensionClass); - } - else if (GroupListener.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind GroupListener {}", extensionClass.getName()); - } - - binder.bind(extensionClass); - groupListenerBinder.addBinding().to(extensionClass); - } - else if (UserListener.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind UserListener {}", extensionClass.getName()); - } - - binder.bind(extensionClass); - userListenerBinder.addBinding().to(extensionClass); - } - else if (RepositoryListener.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind RepositoryListener {}", extensionClass.getName()); - } - - binder.bind(extensionClass); - repositoryListenerBinder.addBinding().to(extensionClass); - } - else if (AuthenticationListener.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind AuthenticaitonListener {}", - extensionClass.getName()); - } - - binder.bind(extensionClass); - authenticationListenerBinder.addBinding().to(extensionClass); - } - else if (ResourceHandler.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind ResourceHandler {}", extensionClass.getName()); - } - - resourceHandler.addBinding().to(extensionClass); - } - else if (FileSystem.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind FileSystem {}", extensionClass.getName()); - } - - fileSystemClass = extensionClass; - } - else if (ChangesetPreProcessor.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind ChangesetPreProcessor {}", - extensionClass.getName()); - } - - changesetPreProcessorBinder.addBinding().to(extensionClass); - } - else if ( - ChangesetPreProcessorFactory.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind ChangesetPreProcessorFactory {}", - extensionClass.getName()); - } - - changesetPreProcessorFactoryBinder.addBinding().to(extensionClass); - } - else if (FileObjectPreProcessor.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind FileObjectPreProcessor {}", - extensionClass.getName()); - } - - fileObjectPreProcessorBinder.addBinding().to(extensionClass); - } - else if ( - FileObjectPreProcessorFactory.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind FileObjectPreProcessorFactory {}", - extensionClass.getName()); - } - - fileObjectPreProcessorFactoryBinder.addBinding().to(extensionClass); - } - else if (BlameLinePreProcessor.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind BlameLinePreProcessor {}", - extensionClass.getName()); - } - - blameLinePreProcessorBinder.addBinding().to(extensionClass); - } - else if ( - BlameLinePreProcessorFactory.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind BlameLinePreProcessorFactory {}", - extensionClass.getName()); - } - - blameLinePreProcessorFactoryBinder.addBinding().to(extensionClass); - } - else if (RepositoryHook.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind RepositoryHook {}", extensionClass.getName()); - } - - repositoryHookBinder.addBinding().to(extensionClass); - } - else if (RepositoryRequestListener.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind RepositoryRequestListener {}", - extensionClass.getName()); - } - - repositoryRequestListenerBinder.addBinding().to(extensionClass); - } - else if (ServletContextListener.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind ServletContextListener {}", - extensionClass.getName()); - } - - servletContextListenerBinder.addBinding().to(extensionClass); - } - else if (RepositoryServiceResolver.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind RepositoryServiceResolver {}", - extensionClass.getName()); - } - - repositoryServiceResolverBinder.addBinding().to(extensionClass); - } - else - { - if (logger.isInfoEnabled()) - { - logger.info("bind {}", extensionClass.getName()); - } - - binder.bind(extensionClass); - } - } - } - - /** - * Method description - * - * - * @param extension - * @param extensionClass - */ - @Override - public void processExtension(Extension extension, Class extensionClass) - { - if (Module.class.isAssignableFrom(extensionClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("add GuiceModule {}", extensionClass.getName()); - } - - addModuleClass(extensionClass); - } - else if (DecoratorFactory.class.isAssignableFrom(extensionClass)) - { - decoratorFactories.add(extensionClass); - } - else - { - extensions.add(extensionClass); - } - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public Class getFileSystemClass() - { - return fileSystemClass; - } - - /** - * Method description - * - * - * @return - */ - public Set getHooks() - { - return hooks; - } - - /** - * Method description - * - * - * @return - */ - public Set getModuleSet() - { - return moduleSet; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param extensionClass - */ - private void addModuleClass(Class extensionClass) - { - try - { - Module module = extensionClass.newInstance(); - - moduleSet.add(module); - } - catch (Exception ex) - { - logger.error(ex.getMessage(), ex); - } - } - - /** - * Method description - * - * - * - * @param binder - * @param type - * @param bindingType - * @param - * - */ - private void bind(Binder binder, Class type, - Class bindingType) - { - if (logger.isDebugEnabled()) - { - logger.debug("bind {} of type {}", type.getName(), bindingType.getName()); - } - - binder.bind(type).to(bindingType); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Set>> decoratorFactories; - - /** Field description */ - private Set> extensions; - - /** Field description */ - private Class fileSystemClass; - - /** Field description */ - private Set hooks = new HashSet(); - - /** Field description */ - private Set moduleSet; -} diff --git a/scm-webapp/src/main/java/sonia/scm/DecoratorBinder.java b/scm-webapp/src/main/java/sonia/scm/DecoratorBinder.java deleted file mode 100644 index af6ae9b043..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/DecoratorBinder.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (c) 2010, 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; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.inject.Binder; -import com.google.inject.multibindings.Multibinder; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.group.GroupManagerDecoratorFactory; -import sonia.scm.repository.RepositoryManagerDecoratorFactory; -import sonia.scm.user.UserManagerDecoratorFactory; - -/** - * - * @author Sebastian Sdorra - */ -public class DecoratorBinder -{ - - /** - * the logger for DecoratorBinder - */ - private static final Logger logger = - LoggerFactory.getLogger(DecoratorBinder.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param binder - */ - public DecoratorBinder(Binder binder) - { - userManagerDecoratorFactories = Multibinder.newSetBinder(binder, - UserManagerDecoratorFactory.class); - groupManagerDecoratorFactories = Multibinder.newSetBinder(binder, - GroupManagerDecoratorFactory.class); - repositoryManagerDecoratorFactories = Multibinder.newSetBinder(binder, - RepositoryManagerDecoratorFactory.class); - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param factoryClass - */ - public void bindFactory(Class factoryClass) - { - if (UserManagerDecoratorFactory.class.isAssignableFrom(factoryClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind user manager decorator {}"); - } - - userManagerDecoratorFactories.addBinding().to(factoryClass); - } - else if (GroupManagerDecoratorFactory.class.isAssignableFrom(factoryClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind group manager decorator {}"); - } - - groupManagerDecoratorFactories.addBinding().to(factoryClass); - } - else if ( - RepositoryManagerDecoratorFactory.class.isAssignableFrom(factoryClass)) - { - if (logger.isInfoEnabled()) - { - logger.info("bind repository manager decorator {}"); - } - - repositoryManagerDecoratorFactories.addBinding().to(factoryClass); - } - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Multibinder groupManagerDecoratorFactories; - - /** Field description */ - private Multibinder repositoryManagerDecoratorFactories; - - /** Field description */ - private Multibinder userManagerDecoratorFactories; -} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index 8f02c3be38..9ba2363721 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -173,15 +173,14 @@ public class ScmContextListener extends GuiceServletContextListener */ private Injector getDefaultInjector(ServletContext servletContext) { - PluginLoader pluginLoader = new DefaultPluginLoader(); - BindingExtensionProcessor bindExtProcessor = - new BindingExtensionProcessor(); + DefaultPluginLoader pluginLoader = new DefaultPluginLoader(servletContext); + //BindingExtensionProcessor bindExtProcessor = + // new BindingExtensionProcessor(); - pluginLoader.processExtensions(bindExtProcessor); + // pluginLoader.processExtensions(bindExtProcessor); ClassOverrides overrides = ClassOverrides.findOverrides(); - ScmServletModule main = new ScmServletModule(pluginLoader, - bindExtProcessor, overrides); + ScmServletModule main = new ScmServletModule(pluginLoader, overrides); List moduleList = Lists.newArrayList(); moduleList.add(new ScmInitializerModule()); @@ -190,7 +189,7 @@ public class ScmContextListener extends GuiceServletContextListener moduleList.add(ShiroWebModule.guiceFilterModule()); moduleList.add(main); moduleList.add(new ScmSecurityModule(servletContext)); - moduleList.addAll(bindExtProcessor.getModuleSet()); + moduleList.addAll(pluginLoader.getModuleSet()); moduleList.addAll(overrides.getModules()); return Guice.createInjector(moduleList); diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 424315e8c4..ec14dbe9c4 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -35,7 +35,6 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.eventbus.EventBus; import com.google.inject.Provider; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; @@ -64,6 +63,7 @@ import sonia.scm.io.DefaultFileSystem; import sonia.scm.io.FileSystem; import sonia.scm.net.HttpClient; import sonia.scm.net.URLHttpClient; +import sonia.scm.plugin.DefaultPluginLoader; import sonia.scm.plugin.DefaultPluginManager; import sonia.scm.plugin.Plugin; import sonia.scm.plugin.PluginLoader; @@ -216,11 +216,9 @@ public class ScmServletModule extends ServletModule * @param bindExtProcessor * @param overrides */ - ScmServletModule(PluginLoader pluginLoader, - BindingExtensionProcessor bindExtProcessor, ClassOverrides overrides) + ScmServletModule(DefaultPluginLoader pluginLoader, ClassOverrides overrides) { this.pluginLoader = pluginLoader; - this.bindExtProcessor = bindExtProcessor; this.overrides = overrides; } @@ -239,9 +237,6 @@ public class ScmServletModule extends ServletModule bind(SCMContextProvider.class).toInstance(context); - // bind decorators - bindExtProcessor.bindDecorators(binder()); - ScmConfiguration config = getScmConfiguration(context); CipherUtil cu = CipherUtil.getInstance(); @@ -266,17 +261,10 @@ public class ScmServletModule extends ServletModule bind(KeyGenerator.class).to(DefaultKeyGenerator.class); bind(CipherHandler.class).toInstance(cu.getCipherHandler()); bind(EncryptionHandler.class, MessageDigestEncryptionHandler.class); - bindExtProcessor.bindExtensions(binder()); + bind(FileSystem.class, DefaultFileSystem.class); - Class fileSystem = - bindExtProcessor.getFileSystemClass(); - - if (fileSystem == null) - { - fileSystem = DefaultFileSystem.class; - } - - bind(FileSystem.class).to(fileSystem); + // bind extensions + pluginLoader.processExtensions(binder()); // bind security stuff bind(AuthenticationManager.class, ChainAuthenticatonManager.class); @@ -583,12 +571,9 @@ public class ScmServletModule extends ServletModule //~--- fields --------------------------------------------------------------- - /** Field description */ - private BindingExtensionProcessor bindExtProcessor; - /** Field description */ private ClassOverrides overrides; /** Field description */ - private PluginLoader pluginLoader; + private DefaultPluginLoader pluginLoader; } 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 71e5f394dd..62388e3454 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java @@ -35,6 +35,12 @@ package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.common.io.Closeables; +import com.google.inject.Binder; +import com.google.inject.Module; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,8 +52,10 @@ import sonia.scm.plugin.ext.AnnotationScanner; import sonia.scm.plugin.ext.AnnotationScannerFactory; import sonia.scm.plugin.ext.DefaultAnnotationScannerFactory; import sonia.scm.plugin.ext.Extension; +import sonia.scm.plugin.ext.ExtensionBinder; import sonia.scm.plugin.ext.ExtensionProcessor; -import sonia.scm.util.IOUtil; +import sonia.scm.plugin.ext.Extensions; +import sonia.scm.web.security.DefaultAuthenticationHandler; //~--- JDK imports ------------------------------------------------------------ @@ -58,6 +66,7 @@ import java.io.InputStream; import java.lang.annotation.Annotation; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; @@ -66,6 +75,9 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.Set; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextListener; + import javax.xml.bind.JAXB; /** @@ -78,9 +90,18 @@ public class DefaultPluginLoader implements PluginLoader /** Field description */ public static final String ENCODING = "UTF-8"; + /** Field description */ + public static final String EXTENSION_JAR = ".jar"; + /** Field description */ public static final String PATH_PLUGINCONFIG = "META-INF/scm/plugin.xml"; + /** Field description */ + public static final String PATH_WEBINFLIB = "/WEB-INF/lib"; + + /** Field description */ + public static final String PATH_SCMCORE = PATH_WEBINFLIB.concat("/scm-core"); + /** Field description */ public static final String REGE_COREPLUGIN = "^.*(?:/|\\\\)WEB-INF(?:/|\\\\)lib(?:/|\\\\).*\\.jar$"; @@ -94,16 +115,22 @@ public class DefaultPluginLoader implements PluginLoader /** * Constructs ... * + * + * @param servletContext */ - public DefaultPluginLoader() + public DefaultPluginLoader(ServletContext servletContext) { + this.servletContext = servletContext; this.annotationScannerFactory = new DefaultAnnotationScannerFactory(); ClassLoader classLoader = getClassLoader(); try { - load(classLoader); + locateCoreFile(); + loadPlugins(classLoader); + scanForAnnotations(); + findModules(); } catch (IOException ex) { @@ -121,22 +148,44 @@ public class DefaultPluginLoader implements PluginLoader * @param packages * @param annotation * @param processor + * @param extensionPointProcessor + * @param extensionProcessor * @param * * @return */ public AnnotationScanner createAnnotationScanner( - ClassLoader classLoader, Collection packages, Class annotation, - AnnotationProcessor processor) + ClassLoader classLoader, Collection packages, + AnnotationProcessor extensionPointProcessor, + AnnotationProcessor extensionProcessor) { AnnotationScanner scanner = annotationScannerFactory.create(classLoader, packages); - scanner.addProcessor(annotation, processor); + if (extensionPointProcessor != null) + { + scanner.addProcessor(ExtensionPoint.class, extensionPointProcessor); + } + + if (extensionProcessor != null) + { + scanner.addProcessor(Extension.class, extensionProcessor); + } return scanner; } + /** + * Method description + * + * + * @param binder + */ + public void processExtensions(Binder binder) + { + new ExtensionBinder(binder).bind(bounds, extensionPoints, extensions); + } + /** * Method description * @@ -146,90 +195,10 @@ public class DefaultPluginLoader implements PluginLoader @Override public void processExtensions(ExtensionProcessor processor) { - ClassLoader classLoader = getClassLoader(); - - AnnotationCollector annotationCollector = - new AnnotationCollector(); - - for (Plugin plugin : installedPlugins) + for (AnnotatedClass extension : extensions) { - if (logger.isDebugEnabled()) - { - logger.debug("search extensions from plugin {}", - plugin.getInformation().getId()); - } - - InputStream input = null; - - try - { - Set packageSet = plugin.getPackageSet(); - - if (packageSet == null) - { - packageSet = new HashSet(); - } - - packageSet.add(SCMContext.DEFAULT_PACKAGE); - - File pluginFile = new File(plugin.getPath()); - - if (pluginFile.exists()) - { - if (logger.isTraceEnabled()) - { - String type = pluginFile.isDirectory() - ? "directory" - : "jar"; - - logger.trace("search extensions in packages {} of {} plugin {}", - new Object[] { packageSet, - type, pluginFile }); - } - - if (pluginFile.isDirectory()) - { - createAnnotationScanner(classLoader, packageSet, Extension.class, - annotationCollector).scanDirectory(pluginFile); - } - else - { - input = new FileInputStream(plugin.getPath()); - createAnnotationScanner(classLoader, packageSet, Extension.class, - annotationCollector).scanArchive(input); - } - } - else - { - logger.error("could not find plugin file {}", plugin.getPath()); - } - } - catch (IOException ex) - { - logger.error("error during extension processing", ex); - } - finally - { - IOUtil.close(input); - } - } - - Set> extensions = - annotationCollector.getAnnotatedClasses(); - - if (logger.isTraceEnabled()) - { - logger.trace("start processing {} extensions", extensions.size()); - } - - for (AnnotatedClass ac : extensions) - { - if (logger.isTraceEnabled()) - { - logger.trace("process extension {}", ac.getAnnotatedClass()); - } - - processor.processExtension(ac.getAnnotation(), ac.getAnnotatedClass()); + processor.processExtension(extension.getAnnotation(), + extension.getAnnotatedClass()); } } @@ -247,8 +216,41 @@ public class DefaultPluginLoader implements PluginLoader return installedPlugins; } + /** + * Method description + * + * + * @return + */ + public Set getModuleSet() + { + return moduleSet; + } + //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @param moduleClass + */ + private void addModule(Class moduleClass) + { + try + { + logger.info("add module {}", moduleClass); + moduleSet.add((Module) moduleClass.newInstance()); + } + catch (Exception ex) + { + logger.error( + "could not create module instance of ".concat(moduleClass.getName()), + ex); + } + + } + /** * Method description * @@ -280,31 +282,47 @@ public class DefaultPluginLoader implements PluginLoader * Method description * * - * @param classLoader + * @param url * - * @throws IOException + * @return */ - private void load(ClassLoader classLoader) throws IOException + private String extractResourcePath(URL url) { - Enumeration urlEnum = classLoader.getResources(PATH_PLUGINCONFIG); + String path = url.toExternalForm(); - if (urlEnum != null) + if (path.startsWith("file:")) { - while (urlEnum.hasMoreElements()) - { - URL url = urlEnum.nextElement(); - - loadPlugin(url); - } - - if (logger.isDebugEnabled()) - { - logger.debug("loaded {} plugins", installedPlugins.size()); - } + path = path.substring("file:".length(), + path.length() - "/META-INF/scm/plugin.xml".length()); } - else if (logger.isWarnEnabled()) + else { - logger.warn("no plugin descriptor found"); + + // jar:file:/some/path/file.jar!/META-INF/scm/plugin.xml + path = path.substring("jar:file:".length(), path.lastIndexOf("!")); + path = decodePath(path); + } + + logger.trace("extrace resource path {} from url {}", path, url); + + return path; + } + + /** + * Method description + * + */ + private void findModules() + { + for (AnnotatedClass extension : extensions) + { + Class extensionClass = extension.getAnnotatedClass(); + + if (Module.class.isAssignableFrom(extensionClass)) + { + bounds.add(extension); + addModule(extensionClass); + } } } @@ -316,7 +334,7 @@ public class DefaultPluginLoader implements PluginLoader */ private void loadPlugin(URL url) { - String path = url.toExternalForm(); + String path = extractResourcePath(url); if (logger.isTraceEnabled()) { @@ -325,19 +343,6 @@ public class DefaultPluginLoader implements PluginLoader try { - if (path.startsWith("file:")) - { - path = path.substring("file:".length(), - path.length() - "/META-INF/scm/plugin.xml".length()); - } - else - { - - // jar:file:/some/path/file.jar!/META-INF/scm/plugin.xml - path = path.substring("jar:file:".length(), path.lastIndexOf("!")); - path = decodePath(path); - } - boolean corePlugin = path.matches(REGE_COREPLUGIN); if (logger.isInfoEnabled()) @@ -378,6 +383,234 @@ public class DefaultPluginLoader implements PluginLoader } } + /** + * Method description + * + * + * @param classLoader + * + * @throws IOException + */ + private void loadPlugins(ClassLoader classLoader) throws IOException + { + Enumeration urlEnum = classLoader.getResources(PATH_PLUGINCONFIG); + + if (urlEnum != null) + { + while (urlEnum.hasMoreElements()) + { + URL url = urlEnum.nextElement(); + + loadPlugin(url); + } + + if (logger.isDebugEnabled()) + { + logger.debug("loaded {} plugins", installedPlugins.size()); + } + } + else if (logger.isWarnEnabled()) + { + logger.warn("no plugin descriptor found"); + } + } + + /** + * Method description + * + * + * @param classLoader + * + * @throws MalformedURLException + */ + private void locateCoreFile() throws MalformedURLException + { + Set paths = servletContext.getResourcePaths(PATH_WEBINFLIB); + + for (String path : paths) + { + if (path.startsWith(PATH_SCMCORE) && path.endsWith(EXTENSION_JAR)) + { + coreFile = servletContext.getResource(path); + + break; + } + } + + if (coreFile == null) + { + throw new IllegalStateException("could not find scm-core file"); + } + } + + /** + * Method description + * + * + * @param classLoader + * @param packageSet + * @param extensionPointCollector + * @param extensionCollector + * @param file + * + * @throws IOException + */ + private void scanFile(ClassLoader classLoader, Collection packageSet, + AnnotationCollector extensionPointCollector, + AnnotationCollector extensionCollector, File file) + throws IOException + { + if (logger.isTraceEnabled()) + { + String type = file.isDirectory() + ? "directory" + : "jar"; + + logger.trace("search extensions in packages {} of {} file {}", + new Object[] { packageSet, + type, file }); + } + + if (file.isDirectory()) + { + createAnnotationScanner(classLoader, packageSet, extensionPointCollector, + extensionCollector).scanDirectory(file); + } + else + { + InputStream input = null; + + try + { + input = new FileInputStream(file); + createAnnotationScanner(classLoader, packageSet, + extensionPointCollector, extensionCollector).scanArchive(input); + } + finally + { + Closeables.closeQuietly(input); + } + } + } + + /** + * Method description + * + * + * @param processor + * + * @param binder + */ + private void scanForAnnotations() + { + ClassLoader classLoader = getClassLoader(); + + AnnotationCollector extensionPointCollector = + new AnnotationCollector(); + AnnotationCollector extensionCollector = + new AnnotationCollector(); + + logger.debug("search extension points in {}", coreFile); + + Set corePackages = ImmutableSet.of("sonia.scm"); + + try + { + scanURL(classLoader, corePackages, extensionPointCollector, null, + coreFile); + } + catch (Exception ex) + { + throw new IllegalStateException("could not process scm-core", ex); + } + + for (Plugin plugin : installedPlugins) + { + if (logger.isDebugEnabled()) + { + logger.debug("search extensions from plugin {}", + plugin.getInformation().getId()); + } + + try + { + Set packageSet = plugin.getPackageSet(); + + if (packageSet == null) + { + packageSet = new HashSet(); + } + + packageSet.add(SCMContext.DEFAULT_PACKAGE); + + File pluginFile = new File(plugin.getPath()); + + if (pluginFile.exists()) + { + scanFile(classLoader, packageSet, extensionPointCollector, + extensionCollector, pluginFile); + } + else + { + logger.error("could not find plugin file {}", plugin.getPath()); + } + } + catch (IOException ex) + { + logger.error("error during extension processing", ex); + } + } + + //J- + extensionPoints = extensionPointCollector.getAnnotatedClasses(); + extensionPoints.add( + new AnnotatedClass( + Extensions.createExtensionPoint(true), + ServletContextListener.class + ) + ); + + extensions = extensionCollector.getAnnotatedClasses(); + extensions.add( + new AnnotatedClass( + Extensions.createExtension(), + DefaultAuthenticationHandler.class + ) + ); + //J+ + } + + /** + * Method description + * + * + * @param classLoader + * @param packageSet + * @param extensionPointCollector + * @param extensionCollector + * @param file + * + * @throws IOException + */ + private void scanURL(ClassLoader classLoader, Collection packageSet, + AnnotationCollector extensionPointCollector, + AnnotationCollector extensionCollector, URL file) + throws IOException + { + InputStream content = null; + + try + { + content = file.openStream(); + createAnnotationScanner(classLoader, packageSet, extensionPointCollector, + extensionCollector).scanArchive(content); + } + finally + { + Closeables.closeQuietly(content); + } + } + //~--- get methods ---------------------------------------------------------- /** @@ -408,6 +641,24 @@ public class DefaultPluginLoader implements PluginLoader /** Field description */ private AnnotationScannerFactory annotationScannerFactory; + /** Field description */ + private Set> bounds = Sets.newHashSet(); + + /** Field description */ + private URL coreFile; + + /** Field description */ + private Set> extensionPoints; + + /** Field description */ + private Set> extensions; + + /** Field description */ + private Set moduleSet = Sets.newHashSet(); + /** Field description */ private Set installedPlugins = new HashSet(); + + /** Field description */ + private ServletContext servletContext; } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScanner.java b/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScanner.java index 86ee6cb9b2..aa71bd297c 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScanner.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScanner.java @@ -30,6 +30,7 @@ */ + package sonia.scm.plugin.ext; //~--- non-JDK imports -------------------------------------------------------- @@ -146,7 +147,7 @@ public class DefaultAnnotationScanner implements AnnotationScanner @Override public void scanDirectory(File directory) { - Preconditions.checkArgument(!directory.isDirectory(), + Preconditions.checkArgument(directory.isDirectory(), "file must be a directory"); String basePath = directory.getAbsolutePath(); @@ -202,8 +203,8 @@ public class DefaultAnnotationScanner implements AnnotationScanner { if (logger.isDebugEnabled()) { - logger.debug("call processor {} with {} and {}", - ap.getClass(), annotation, managedClass); + logger.debug("call processor {} with {} and {}", ap.getClass(), + annotation, managedClass); } ap.processAnnotation(annotation, managedClass); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ext/ExtensionBinder.java b/scm-webapp/src/main/java/sonia/scm/plugin/ext/ExtensionBinder.java new file mode 100644 index 0000000000..eec679cbe8 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ext/ExtensionBinder.java @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2010, 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.ext; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.inject.Binder; +import com.google.inject.multibindings.Multibinder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.plugin.ExtensionPoint; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Set; + +/** + * + * @author Sebastian Sdorra + */ +public class ExtensionBinder +{ + + /** + * the logger for ExtensionBinder + */ + private static final Logger logger = + LoggerFactory.getLogger(ExtensionBinder.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param binder + */ + public ExtensionBinder(Binder binder) + { + this.binder = binder; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * + * @param bounds + * @param extensionPoints + * @param extensions + */ + public void bind(Set> bounds, + Set> extensionPoints, + Set> extensions) + { + if (logger.isInfoEnabled()) + { + logger.info("bind {} extensions to {} extension points", + extensions.size(), extensionPoints.size()); + } + + for (AnnotatedClass extensionPoint : extensionPoints) + { + ExtensionPoint extensionPointAnnotation = extensionPoint.getAnnotation(); + Class extensionPointClass = extensionPoint.getAnnotatedClass(); + + if (extensionPointAnnotation.multi()) + { + bindMultiExtensionPoint(bounds, extensionPointClass, extensions); + } + else + { + bindExtensionPoint(bounds, extensionPointClass, extensions); + } + } + + Set> extensionsCopy = Sets.newHashSet(extensions); + + Iterables.removeAll(extensionsCopy, bounds); + + for (AnnotatedClass extension : extensionsCopy) + { + logger.info("bind {}, without extensionpoint", + extension.getAnnotatedClass()); + binder.bind(extension.getAnnotatedClass()); + } + } + + /** + * Method description + * + * + * + * @param found + * @param extensionPointClass + * @param extensions + */ + private void bindExtensionPoint(Set> found, + Class extensionPointClass, Set> extensions) + { + for (AnnotatedClass extension : extensions) + { + Class extensionClass = extension.getAnnotatedClass(); + + if (extensionPointClass.isAssignableFrom(extensionClass)) + { + found.add(extension); + bindSingleInstance(extensionPointClass, extensionClass); + + break; + } + } + } + + /** + * Method description + * + * + * + * @param found + * @param extensionPointClass + * @param extensions + */ + private void bindMultiExtensionPoint(Set> found, + Class extensionPointClass, Set> extensions) + { + if (logger.isInfoEnabled()) + { + logger.info("create multibinder for {}", extensionPointClass.getName()); + } + + Multibinder multibinder = Multibinder.newSetBinder(binder, + extensionPointClass); + + for (AnnotatedClass extension : extensions) + { + Class extensionClass = extension.getAnnotatedClass(); + + if (extensionPointClass.isAssignableFrom(extensionClass)) + { + if (logger.isInfoEnabled()) + { + logger.info("bind {} to multibinder of {}", extensionClass.getName(), + extensionPointClass.getName()); + } + + found.add(extension); + multibinder.addBinding().to(extensionClass); + } + } + } + + /** + * Method description + * + * + * @param extensionPointClass + * @param extensionClass + */ + private void bindSingleInstance(Class extensionPointClass, + Class extensionClass) + { + if (logger.isInfoEnabled()) + { + logger.info("bind {} to {}", extensionClass.getName(), + extensionPointClass.getName()); + } + + binder.bind(extensionPointClass).to(extensionClass); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private Binder binder; +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ext/Extensions.java b/scm-webapp/src/main/java/sonia/scm/plugin/ext/Extensions.java new file mode 100644 index 0000000000..f0e73e1167 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ext/Extensions.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2010, 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.ext; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.plugin.ExtensionPoint; + +//~--- JDK imports ------------------------------------------------------------ + +import java.lang.annotation.Annotation; + +/** + * + * @author Sebastian Sdorra + */ +public class Extensions +{ + + /** + * Method description + * + * + * @return + */ + public static Extension createExtension() + { + return new Extension() + { + + @Override + public Class annotationType() + { + return Extension.class; + } + }; + } + + /** + * Method description + * + * + * @param multi + * + * @return + */ + public static ExtensionPoint createExtensionPoint(final boolean multi) + { + return new ExtensionPoint() + { + + @Override + public boolean multi() + { + return multi; + } + + @Override + public Class annotationType() + { + return ExtensionPoint.class; + } + }; + } +}