From 6f5780b9dfdd7970fa49819413a7b4dc60c98938 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 15 Jan 2013 16:55:07 +0100 Subject: [PATCH] implementing an annotation scanner to replace the extension scanner --- .../sonia/scm/plugin/ext/AnnotatedClass.java | 93 +++++ .../scm/plugin/ext/AnnotationCollector.java | 86 ++++ .../scm/plugin/ext/AnnotationProcessor.java | 57 +++ .../scm/plugin/ext/AnnotationScanner.java | 83 ++++ .../plugin/ext/AnnotationScannerFactory.java | 60 +++ .../sonia/scm/plugin/DefaultPluginLoader.java | 88 ++-- .../plugin/ext/DefaultAnnotationScanner.java | 390 ++++++++++++++++++ .../ext/DefaultAnnotationScannerFactory.java | 60 +++ .../src/main/resources/logback.default.xml | 1 + 9 files changed, 891 insertions(+), 27 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ext/AnnotatedClass.java create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationCollector.java create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationProcessor.java create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScanner.java create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScannerFactory.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScanner.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScannerFactory.java 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 new file mode 100644 index 0000000000..cbe8cf003d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotatedClass.java @@ -0,0 +1,93 @@ +/** + * 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 + */ +public class AnnotatedClass +{ + + /** + * 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; +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationCollector.java b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationCollector.java new file mode 100644 index 0000000000..9067867d2c --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationCollector.java @@ -0,0 +1,86 @@ +/** + * 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 + */ +public class AnnotationCollector + implements AnnotationProcessor +{ + + /** + * 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(annotation, annotatedClass)); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns the annotated classes. + * + * + * @return set of annotated classes + */ + public Set> getAnnotatedClasses() + { + return annotatedClasses; + } + + //~--- fields --------------------------------------------------------------- + + /** set of annotated classes */ + private Set> annotatedClasses = Sets.newHashSet(); +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationProcessor.java b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationProcessor.java new file mode 100644 index 0000000000..a9f3e46ad1 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationProcessor.java @@ -0,0 +1,57 @@ +/** + * 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 + */ +public interface AnnotationProcessor +{ + + /** + * Process annotated class. + * + * + * @param annotation found annotation + * @param annotatedClass annotated class + */ + public void processAnnotation(T annotation, Class annotatedClass); +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScanner.java b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScanner.java new file mode 100644 index 0000000000..d093a8fc0d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScanner.java @@ -0,0 +1,83 @@ +/** + * 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 annotation type + */ + public void addProcessor(Class annotationClass, + AnnotationProcessor 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; +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScannerFactory.java b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScannerFactory.java new file mode 100644 index 0000000000..d70994ec93 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/AnnotationScannerFactory.java @@ -0,0 +1,60 @@ +/** + * 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 packages); +} 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 adbfc353e6..71e5f394dd 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,13 @@ import org.slf4j.Logger; 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; @@ -51,6 +56,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Annotation; + import java.net.URL; import java.net.URLDecoder; @@ -90,6 +97,8 @@ public class DefaultPluginLoader implements PluginLoader */ public DefaultPluginLoader() { + this.annotationScannerFactory = new DefaultAnnotationScannerFactory(); + ClassLoader classLoader = getClassLoader(); try @@ -104,6 +113,30 @@ public class DefaultPluginLoader implements PluginLoader //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @param classLoader + * @param packages + * @param annotation + * @param processor + * @param + * + * @return + */ + public AnnotationScanner createAnnotationScanner( + ClassLoader classLoader, Collection packages, Class annotation, + AnnotationProcessor processor) + { + AnnotationScanner scanner = annotationScannerFactory.create(classLoader, + packages); + + scanner.addProcessor(annotation, processor); + + return scanner; + } + /** * Method description * @@ -113,16 +146,17 @@ public class DefaultPluginLoader implements PluginLoader @Override public void processExtensions(ExtensionProcessor processor) { - Set extensions = new HashSet(); ClassLoader classLoader = getClassLoader(); - DefaultExtensionScanner scanner = new DefaultExtensionScanner(); + + AnnotationCollector annotationCollector = + new AnnotationCollector(); for (Plugin plugin : installedPlugins) { if (logger.isDebugEnabled()) { logger.debug("search extensions from plugin {}", - plugin.getInformation().getId()); + plugin.getInformation().getId()); } InputStream input = null; @@ -145,24 +179,24 @@ public class DefaultPluginLoader implements PluginLoader 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 @@ -180,19 +214,22 @@ public class DefaultPluginLoader implements PluginLoader } } + Set> extensions = + annotationCollector.getAnnotatedClasses(); + if (logger.isTraceEnabled()) { logger.trace("start processing {} extensions", extensions.size()); } - for (ExtensionObject exo : extensions) + for (AnnotatedClass 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()); } } @@ -235,11 +272,6 @@ public class DefaultPluginLoader implements PluginLoader 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; } @@ -296,8 +328,7 @@ public class DefaultPluginLoader implements PluginLoader 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 { @@ -312,8 +343,8 @@ public class DefaultPluginLoader implements PluginLoader if (logger.isInfoEnabled()) { logger.info("load {}plugin {}", corePlugin - ? "core " - : " ", path); + ? "core " + : " ", path); } Plugin plugin = JAXB.unmarshal(url, Plugin.class); @@ -328,8 +359,8 @@ public class DefaultPluginLoader implements PluginLoader if (info != null) { info.setState(corePlugin - ? PluginState.CORE - : PluginState.INSTALLED); + ? PluginState.CORE + : PluginState.INSTALLED); } plugin.setPath(path); @@ -374,6 +405,9 @@ public class DefaultPluginLoader implements PluginLoader //~--- fields --------------------------------------------------------------- + /** Field description */ + private AnnotationScannerFactory annotationScannerFactory; + /** Field description */ private Set installedPlugins = new HashSet(); } 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 new file mode 100644 index 0000000000..5182209ea9 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScanner.java @@ -0,0 +1,390 @@ +/** + * 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 packages) + { + this.classLoader = classLoader; + this.packages = packages; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param annotationClass + * @param processor + * @param + */ + @Override + public void addProcessor(Class annotationClass, + AnnotationProcessor 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 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 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 packages; + + /** Field description */ + private Multimap processors = + HashMultimap.create(); +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScannerFactory.java b/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScannerFactory.java new file mode 100644 index 0000000000..64433ea7ff --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ext/DefaultAnnotationScannerFactory.java @@ -0,0 +1,60 @@ +/** + * 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 packages) + { + return new DefaultAnnotationScanner(classLoader, packages); + } +} diff --git a/scm-webapp/src/main/resources/logback.default.xml b/scm-webapp/src/main/resources/logback.default.xml index 8ede379ac6..10fd6951a6 100644 --- a/scm-webapp/src/main/resources/logback.default.xml +++ b/scm-webapp/src/main/resources/logback.default.xml @@ -57,6 +57,7 @@ +