From c32e421394bfa9c07015f76a2ea989a9d1644fc1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 24 Jul 2011 13:09:29 +0200 Subject: [PATCH] improve scann for extensions --- .../plugin/ext/DefaultExtensionScanner.java | 333 ++++++++++++++++++ .../plugin/ext/DirectoryExtensionScanner.java | 66 ++++ .../scm/plugin/ext/JARExtensionScanner.java | 2 + .../sonia/scm/plugin/DefaultPluginLoader.java | 31 +- 4 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ext/DefaultExtensionScanner.java create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ext/DirectoryExtensionScanner.java diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/DefaultExtensionScanner.java b/scm-core/src/main/java/sonia/scm/plugin/ext/DefaultExtensionScanner.java new file mode 100644 index 0000000000..12668011eb --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/DefaultExtensionScanner.java @@ -0,0 +1,333 @@ +/** + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.util.IOUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import java.util.Collection; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + * + * @author Sebastian Sdorra + * @since 1.6 + */ +public class DefaultExtensionScanner + implements ExtensionScanner, DirectoryExtensionScanner +{ + + /** the logger for DefaultExtensionScanner */ + private static final Logger logger = + LoggerFactory.getLogger(DefaultExtensionScanner.class); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param classLoader + * @param extensionObjects + * @param inputStream + * @param packages + * + * @throws IOException + */ + @Override + public void processExtensions(ClassLoader classLoader, + Collection extensionObjects, + InputStream inputStream, + Collection packages) + throws IOException + { + JarInputStream input = null; + + try + { + input = new JarInputStream(inputStream); + + JarEntry entry = input.getNextJarEntry(); + + while (entry != null) + { + if (!entry.isDirectory()) + { + processEntry(classLoader, extensionObjects, packages, entry); + } + + entry = input.getNextJarEntry(); + } + } + finally + { + IOUtil.close(input); + } + } + + /** + * Method description + * + * + * @param classLoader + * @param extensionObjects + * @param directory + * @param packages + * + * @throws IOException + */ + @Override + public void processExtensions(ClassLoader classLoader, + Collection extensionObjects, + File directory, Collection packages) + throws IOException + { + String basePath = directory.getAbsolutePath(); + + if (directory.exists() && directory.isDirectory()) + { + processDirectory(classLoader, extensionObjects, directory, packages, + basePath); + } + else + { + logger.error("directory is required"); + } + } + + /** + * Method description + * + * + * @param classLoader + * @param name + * + * @return + */ + private Class createClass(ClassLoader classLoader, 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 classLoader + * @param extensionObjects + * @param packages + * @param name + */ + private void processClass(ClassLoader classLoader, + Collection extensionObjects, + Collection packages, String name) + { + if (isManagedClass(packages, name)) + { + Class managedClass = createClass(classLoader, name); + + if (managedClass != null) + { + processManagedClass(extensionObjects, managedClass); + } + } + } + + /** + * Method description + * + * + * @param classLoader + * @param extensionObjects + * @param packages + * @param basePath + * @param file + */ + private void processClassFile(ClassLoader classLoader, + Collection extensionObjects, + Collection packages, String basePath, + File file) + { + String name = file.getAbsolutePath().substring(basePath.length()); + + if (name.startsWith("/")) + { + name = name.substring(1); + } + + name = getClassName(name); + processClass(classLoader, extensionObjects, packages, name); + } + + /** + * Method description + * + * + * @param classLoader + * @param extensionObjects + * @param directory + * @param packages + * @param basePath + */ + private void processDirectory(ClassLoader classLoader, + Collection extensionObjects, + File directory, Collection packages, + String basePath) + { + File[] children = directory.listFiles(); + + for (File child : children) + { + if (child.isDirectory()) + { + processDirectory(classLoader, extensionObjects, child, packages, + basePath); + } + else if (child.getName().endsWith(".class")) + { + processClassFile(classLoader, extensionObjects, packages, basePath, + child); + } + } + } + + /** + * Method description + * + * + * + * @param classLoader + * @param extensionObjects + * @param packages + * @param entry + */ + private void processEntry(ClassLoader classLoader, + Collection extensionObjects, + Collection packages, JarEntry entry) + { + String name = entry.getName(); + + if (name.endsWith(".class")) + { + name = getClassName(name); + processClass(classLoader, extensionObjects, packages, name); + } + } + + /** + * Method description + * + * + * @param extensionObjects + * @param managedClass + */ + private void processManagedClass( + Collection extensionObjects, Class managedClass) + { + Extension extension = managedClass.getAnnotation(Extension.class); + + if (extension != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("found extension class {}", managedClass.getName()); + } + + extensionObjects.add(new ExtensionObject(extension, 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; + } +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/DirectoryExtensionScanner.java b/scm-core/src/main/java/sonia/scm/plugin/ext/DirectoryExtensionScanner.java new file mode 100644 index 0000000000..9cfe4f7647 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/DirectoryExtensionScanner.java @@ -0,0 +1,66 @@ +/** + * 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.util.Collection; + +/** + * + * @author Sebastian Sdorra + * @since 1.6 + */ +public interface DirectoryExtensionScanner +{ + + /** + * Method description + * + * + * @param classLoader + * @param extensionObjects + * @param directory + * @param packages + * + * @throws IOException + */ + public void processExtensions(ClassLoader classLoader, + Collection extensionObjects, + File directory, Collection packages) + throws IOException; +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ext/JARExtensionScanner.java b/scm-core/src/main/java/sonia/scm/plugin/ext/JARExtensionScanner.java index 55b60b69d2..8081d5b05b 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ext/JARExtensionScanner.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ext/JARExtensionScanner.java @@ -52,7 +52,9 @@ import java.util.jar.JarInputStream; /** * * @author Sebastian Sdorra + * @deprecated use {@link DefaultExtensionScanner} instead */ +@Deprecated public class JARExtensionScanner implements ExtensionScanner { 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 92865326d1..c7373e7e59 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java @@ -39,9 +39,9 @@ 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.ExtensionProcessor; -import sonia.scm.plugin.ext.JARExtensionScanner; import sonia.scm.util.IOUtil; //~--- JDK imports ------------------------------------------------------------ @@ -115,7 +115,7 @@ public class DefaultPluginLoader implements PluginLoader { Set extensions = new HashSet(); ClassLoader classLoader = getClassLoader(); - JARExtensionScanner scanner = new JARExtensionScanner(); + DefaultExtensionScanner scanner = new DefaultExtensionScanner(); for (Plugin plugin : installedPlugins) { @@ -131,8 +131,27 @@ public class DefaultPluginLoader implements PluginLoader } packageSet.add(SCMContext.DEFAULT_PACKAGE); - input = new FileInputStream(plugin.getPath()); - scanner.processExtensions(classLoader, extensions, input, packageSet); + + File pluginFile = new File(plugin.getPath()); + + if (pluginFile.exists()) + { + if (pluginFile.isDirectory()) + { + scanner.processExtensions(classLoader, extensions, pluginFile, + packageSet); + } + else + { + input = new FileInputStream(plugin.getPath()); + scanner.processExtensions(classLoader, extensions, input, + packageSet); + } + } + else + { + logger.error("could not find plugin file {}", plugin.getPath()); + } } catch (IOException ex) { @@ -230,7 +249,9 @@ public class DefaultPluginLoader implements PluginLoader { if (path.startsWith("file:")) { - path = path.substring("file:".length()); + path = path.substring("file:".length(), + path.length() + - "/META-INF/scm/plugin.xml".length()); } else {