Sebastian Sdorra avatar Sebastian Sdorra committed 8063d06

implementing an annotation scanner to replace the extension scanner

Comments (0)

Files changed (9)

scm-core/src/main/java/sonia/scm/plugin/ext/AnnotatedClass.java

+/**
+ * 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;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Represents a annotated class, collected by a {@link AnnotationCollector}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.26
+ *
+ * @param <T>
+ */
+public class AnnotatedClass<T extends Annotation>
+{
+
+  /**
+   * Constructs a new annotated class.
+   *
+   *
+   * @param annotation found annotation
+   * @param annotatedClass annotated class
+   */
+  public AnnotatedClass(T annotation, Class<?> annotatedClass)
+  {
+    this.annotation = annotation;
+    this.annotatedClass = annotatedClass;
+  }
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Returns the annotated class.
+   *
+   *
+   * @return annotated class
+   */
+  public Class<?> getAnnotatedClass()
+  {
+    return annotatedClass;
+  }
+
+  /**
+   * Returns the annotation.
+   *
+   *
+   * @return annotation
+   */
+  public T getAnnotation()
+  {
+    return annotation;
+  }
+
+  //~--- fields ---------------------------------------------------------------
+
+  /** annotated class */
+  private Class<?> annotatedClass;
+
+  /** annotation */
+  private T annotation;
+}

scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationCollector.java

+/**
+ * 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.Sets;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.lang.annotation.Annotation;
+
+import java.util.Set;
+
+/**
+ * Collects all annotated classes found by a {@link AnnotationScanner}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.26
+ *
+ * @param <T>
+ */
+public class AnnotationCollector<T extends Annotation>
+  implements AnnotationProcessor<T>
+{
+
+  /**
+   * Creates a new annotation collector.
+   *
+   *
+   * @param annotation found annotation
+   * @param annotatedClass annotated class
+   */
+  @Override
+  public void processAnnotation(T annotation, Class<?> annotatedClass)
+  {
+    annotatedClasses.add(new AnnotatedClass<T>(annotation, annotatedClass));
+  }
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Returns the annotated classes.
+   *
+   *
+   * @return set of annotated classes
+   */
+  public Set<AnnotatedClass<T>> getAnnotatedClasses()
+  {
+    return annotatedClasses;
+  }
+
+  //~--- fields ---------------------------------------------------------------
+
+  /** set of annotated classes */
+  private Set<AnnotatedClass<T>> annotatedClasses = Sets.newHashSet();
+}

scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationProcessor.java

+/**
+ * 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;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Processor for annotated classes found by a {@link AnnotationScanner}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.26
+ *
+ * @param <T>
+ */
+public interface AnnotationProcessor<T extends Annotation>
+{
+
+  /**
+   * Process annotated class.
+   *
+   *
+   * @param annotation found annotation
+   * @param annotatedClass annotated class
+   */
+  public void processAnnotation(T annotation, Class<?> annotatedClass);
+}

scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScanner.java

+/**
+ * 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;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * The annotation scanner is able to scan archives and directories for
+ * annotated classes. Each annotated class can be processed with a
+ * {@link AnnotationProcessor}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.26
+ */
+public interface AnnotationScanner
+{
+
+  /**
+   * Adds a {@link AnnotationProcessor} for the given annotation.
+   *
+   *
+   * @param annotationClass class of the annotation
+   * @param processor processor
+   * @param <T> annotation type
+   */
+  public <T extends Annotation> void addProcessor(Class<T> annotationClass,
+    AnnotationProcessor<T> processor);
+
+  /**
+   * Scans the given archive for annotations.
+   *
+   *
+   * @param archive archive input stream
+   *
+   * @throws IOException
+   */
+  public void scanArchive(InputStream archive) throws IOException;
+
+  /**
+   * Scans a directory for annotated classes.
+   *
+   *
+   * @param directory directory to scan
+   *
+   * @throws IOException
+   */
+  public void scanDirectory(File directory) throws IOException;
+}

scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScannerFactory.java

+/**
+ * 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;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.util.Collection;
+
+/**
+ * Factory class for {@link AnnotationScanner}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.26
+ */
+public interface AnnotationScannerFactory
+{
+
+  /**
+   * Returns a new {@link AnnotationScanner} for the given class loader 
+   * and packages.
+   *
+   *
+   *
+   * @param classLoader class loader
+   * @param packages packages to scann
+   *
+   * @return new annotation scanner
+   */
+  public AnnotationScanner create(ClassLoader classLoader,
+    Collection<String> packages);
+}

scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java

 import org.slf4j.LoggerFactory;
 
 import sonia.scm.SCMContext;
-import sonia.scm.plugin.ext.DefaultExtensionScanner;
-import sonia.scm.plugin.ext.ExtensionObject;
+import sonia.scm.plugin.ext.AnnotatedClass;
+import sonia.scm.plugin.ext.AnnotationCollector;
+import sonia.scm.plugin.ext.AnnotationProcessor;
+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.ExtensionProcessor;
 import sonia.scm.util.IOUtil;
 
 import java.io.IOException;
 import java.io.InputStream;
 
+import java.lang.annotation.Annotation;
+
 import java.net.URL;
 import java.net.URLDecoder;
 
    */
   public DefaultPluginLoader()
   {
+    this.annotationScannerFactory = new DefaultAnnotationScannerFactory();
+
     ClassLoader classLoader = getClassLoader();
 
     try
    * Method description
    *
    *
+   * @param classLoader
+   * @param packages
+   * @param annotation
+   * @param processor
+   * @param <T>
+   *
+   * @return
+   */
+  public <T extends Annotation> AnnotationScanner createAnnotationScanner(
+    ClassLoader classLoader, Collection<String> packages, Class<T> annotation,
+    AnnotationProcessor<T> processor)
+  {
+    AnnotationScanner scanner = annotationScannerFactory.create(classLoader,
+                                  packages);
+
+    scanner.addProcessor(annotation, processor);
+
+    return scanner;
+  }
+
+  /**
+   * Method description
+   *
+   *
    * @param processor
    */
   @Override
   public void processExtensions(ExtensionProcessor processor)
   {
-    Set<ExtensionObject> extensions = new HashSet<ExtensionObject>();
     ClassLoader classLoader = getClassLoader();
-    DefaultExtensionScanner scanner = new DefaultExtensionScanner();
+
+    AnnotationCollector<Extension> annotationCollector =
+      new AnnotationCollector<Extension>();
 
     for (Plugin plugin : installedPlugins)
     {
       if (logger.isDebugEnabled())
       {
         logger.debug("search extensions from plugin {}",
-                     plugin.getInformation().getId());
+          plugin.getInformation().getId());
       }
 
       InputStream input = null;
           if (logger.isTraceEnabled())
           {
             String type = pluginFile.isDirectory()
-                          ? "directory"
-                          : "jar";
+              ? "directory"
+              : "jar";
 
             logger.trace("search extensions in packages {} of {} plugin {}",
-                         new Object[] { packageSet,
-                                        type, pluginFile });
+              new Object[] { packageSet,
+              type, pluginFile });
           }
 
           if (pluginFile.isDirectory())
           {
-            scanner.processExtensions(classLoader, extensions, pluginFile,
-                                      packageSet);
+            createAnnotationScanner(classLoader, packageSet, Extension.class,
+              annotationCollector).scanDirectory(pluginFile);
           }
           else
           {
             input = new FileInputStream(plugin.getPath());
-            scanner.processExtensions(classLoader, extensions, input,
-                                      packageSet);
+            createAnnotationScanner(classLoader, packageSet, Extension.class,
+              annotationCollector).scanArchive(input);
           }
         }
         else
       }
     }
 
+    Set<AnnotatedClass<Extension>> extensions =
+      annotationCollector.getAnnotatedClasses();
+
     if (logger.isTraceEnabled())
     {
       logger.trace("start processing {} extensions", extensions.size());
     }
 
-    for (ExtensionObject exo : extensions)
+    for (AnnotatedClass<Extension> ac : extensions)
     {
       if (logger.isTraceEnabled())
       {
-        logger.trace("process extension {}", exo.getExtensionClass());
+        logger.trace("process extension {}", ac.getAnnotatedClass());
       }
 
-      processor.processExtension(exo.getExtension(), exo.getExtensionClass());
+      processor.processExtension(ac.getAnnotation(), ac.getAnnotatedClass());
     }
   }
 
         logger.error("could not decode path ".concat(path), ex);
       }
     }
-    else if (logger.isTraceEnabled())
-    {
-      logger.trace(
-          "{} seems not to be a file path or the file does not exists", path);
-    }
 
     return path;
   }
       if (path.startsWith("file:"))
       {
         path = path.substring("file:".length(),
-                              path.length()
-                              - "/META-INF/scm/plugin.xml".length());
+          path.length() - "/META-INF/scm/plugin.xml".length());
       }
       else
       {
       if (logger.isInfoEnabled())
       {
         logger.info("load {}plugin {}", corePlugin
-                                         ? "core "
-                                         : " ", path);
+          ? "core "
+          : " ", path);
       }
 
       Plugin plugin = JAXB.unmarshal(url, Plugin.class);
       if (info != null)
       {
         info.setState(corePlugin
-                      ? PluginState.CORE
-                      : PluginState.INSTALLED);
+          ? PluginState.CORE
+          : PluginState.INSTALLED);
       }
 
       plugin.setPath(path);
   //~--- fields ---------------------------------------------------------------
 
   /** Field description */
+  private AnnotationScannerFactory annotationScannerFactory;
+
+  /** Field description */
   private Set<Plugin> installedPlugins = new HashSet<Plugin>();
 }

scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScanner.java

+/**
+ * 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.base.Preconditions;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import sonia.scm.util.IOUtil;
+import sonia.scm.util.Util;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.lang.annotation.Annotation;
+
+import java.util.Collection;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+public class DefaultAnnotationScanner implements AnnotationScanner
+{
+
+  /**
+   * the logger for DefaultAnnotationScanner
+   */
+  private static final Logger logger =
+    LoggerFactory.getLogger(DefaultAnnotationScanner.class);
+
+  //~--- constructors ---------------------------------------------------------
+
+  /**
+   * Constructs ...
+   *
+   *
+   * @param classLoader
+   * @param packages
+   */
+  public DefaultAnnotationScanner(ClassLoader classLoader,
+    Collection<String> packages)
+  {
+    this.classLoader = classLoader;
+    this.packages = packages;
+  }
+
+  //~--- methods --------------------------------------------------------------
+
+  /**
+   * Method description
+   *
+   *
+   * @param annotationClass
+   * @param processor
+   * @param <T>
+   */
+  @Override
+  public <T extends Annotation> void addProcessor(Class<T> annotationClass,
+    AnnotationProcessor<T> processor)
+  {
+    processors.put(annotationClass, processor);
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param archive
+   *
+   * @throws IOException
+   */
+  @Override
+  public void scanArchive(InputStream archive) throws IOException
+  {
+    JarInputStream input = null;
+
+    try
+    {
+      input = new JarInputStream(archive);
+
+      JarEntry entry = input.getNextJarEntry();
+
+      while (entry != null)
+      {
+        if (!entry.isDirectory())
+        {
+          processEntry(entry);
+        }
+
+        entry = input.getNextJarEntry();
+      }
+    }
+    finally
+    {
+      IOUtil.close(input);
+    }
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param directory
+   */
+  @Override
+  public void scanDirectory(File directory)
+  {
+    Preconditions.checkArgument(!directory.isDirectory(),
+      "file must be a directory");
+
+    String basePath = directory.getAbsolutePath();
+
+    processDirectory(basePath, directory);
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param classLoader
+   * @param name
+   *
+   * @return
+   */
+  private Class<?> createClass(String name)
+  {
+    Class<?> clazz = null;
+
+    try
+    {
+      clazz = classLoader.loadClass(name);
+    }
+    catch (Exception ex)
+    {
+      logger.error("could not class ".concat(name), ex);
+    }
+
+    return clazz;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   *
+   * @param annotationClass
+   * @param annotation
+   * @param managedClass
+   */
+  private void processAnnotation(Class<?> annotationClass,
+    Annotation annotation, Class<?> managedClass)
+  {
+    logger.trace("process annotation {} on class {}", annotationClass,
+      managedClass);
+
+    Collection<AnnotationProcessor> aps = processors.get(annotationClass);
+
+    if (Util.isNotEmpty(aps))
+    {
+      for (AnnotationProcessor ap : aps)
+      {
+        if (logger.isDebugEnabled())
+        {
+          logger.debug("call processor {} with {} and {}",
+            ap.getClass(), annotation, managedClass);
+        }
+
+        ap.processAnnotation(annotation, managedClass);
+      }
+    }
+    else if (logger.isTraceEnabled())
+    {
+      logger.trace("no processor found for annotation {}",
+        annotation.getClass());
+    }
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param classLoader
+   * @param extensionObjects
+   * @param packages
+   * @param name
+   */
+  private void processClass(String name)
+  {
+    if (isManagedClass(packages, name))
+    {
+      Class<?> managedClass = createClass(name);
+
+      if (managedClass != null)
+      {
+        processManagedClass(managedClass);
+      }
+    }
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param classLoader
+   * @param extensionObjects
+   * @param packages
+   * @param basePath
+   * @param file
+   */
+  private void processClassFile(String basePath, File file)
+  {
+    String name = file.getAbsolutePath().substring(basePath.length());
+
+    if (name.startsWith("/"))
+    {
+      name = name.substring(1);
+    }
+
+    name = getClassName(name);
+    processClass(name);
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param classLoader
+   * @param extensionObjects
+   * @param directory
+   * @param packages
+   * @param basePath
+   */
+  private void processDirectory(String basePath, File directory)
+  {
+    File[] children = directory.listFiles();
+
+    for (File child : children)
+    {
+      if (child.isDirectory())
+      {
+        processDirectory(basePath, child);
+      }
+      else if (child.getName().endsWith(".class"))
+      {
+        processClassFile(basePath, child);
+      }
+    }
+  }
+
+  /**
+   * Method description
+   *
+   *
+   *
+   * @param classLoader
+   * @param extensionObjects
+   * @param packages
+   * @param entry
+   */
+  private void processEntry(JarEntry entry)
+  {
+    String name = entry.getName();
+
+    if (name.endsWith(".class"))
+    {
+      name = getClassName(name);
+      processClass(name);
+    }
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param extensionObjects
+   * @param managedClass
+   */
+  private void processManagedClass(Class<?> managedClass)
+  {
+    logger.trace("check managed class {} for annotations", managedClass);
+
+    for (Class annotationClass : processors.keySet())
+    {
+      Annotation annotation = managedClass.getAnnotation(annotationClass);
+
+      if (annotation != null)
+      {
+        processAnnotation(annotationClass, annotation, managedClass);
+      }
+      else if (logger.isTraceEnabled())
+      {
+        logger.trace("annotation {} not found at {}", annotationClass,
+          managedClass);
+      }
+    }
+  }
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Method description
+   *
+   *
+   * @param name
+   *
+   * @return
+   */
+  private String getClassName(String name)
+  {
+    return name.replaceAll("/", ".").substring(0, name.length() - 6);
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param packages
+   * @param name
+   *
+   * @return
+   */
+  private boolean isManagedClass(Collection<String> packages, String name)
+  {
+    boolean result = false;
+
+    for (String pkg : packages)
+    {
+      if (name.startsWith(pkg))
+      {
+        result = true;
+
+        break;
+      }
+    }
+
+    return result;
+  }
+
+  //~--- fields ---------------------------------------------------------------
+
+  /** Field description */
+  private ClassLoader classLoader;
+
+  /** Field description */
+  private Collection<String> packages;
+
+  /** Field description */
+  private Multimap<Class, AnnotationProcessor> processors =
+    HashMultimap.create();
+}

scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScannerFactory.java

+/**
+ * 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;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.util.Collection;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+public class DefaultAnnotationScannerFactory implements AnnotationScannerFactory
+{
+
+  /**
+   * Method description
+   *
+   *
+   * @param classLoader
+   * @param packages
+   *
+   * @return
+   */
+  @Override
+  public AnnotationScanner create(ClassLoader classLoader,
+    Collection<String> packages)
+  {
+    return new DefaultAnnotationScanner(classLoader, packages);
+  }
+}

scm-webapp/src/main/resources/logback.default.xml

   <logger name="sonia.scm.filter.GZipResponseStream" level="WARN" />
   
   <logger name="sonia.scm.util.ServiceUtil" level="WARN" />
+  <logger name="sonia.scm.plugin.ext.DefaultAnnotationScanner" level="INFO" />
   
   <!-- aether -->
   <!--
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.