diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java b/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java index d5789391e2..49671b803c 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java @@ -38,6 +38,7 @@ package sonia.scm.plugin; import com.google.inject.Binder; import com.google.inject.Module; + //~--- JDK imports ------------------------------------------------------------ import java.util.Collection; @@ -81,13 +82,23 @@ public interface PluginLoader * * @return */ - public Collection getInstalledPlugins(); + public Collection getInstalledPlugins(); /** - * Method description + * Returns a {@link ClassLoader} which is able to load classes and resources + * from the webapp and all installed plugins. * * - * @return + * @return uber classloader */ public ClassLoader getUberClassLoader(); + + /** + * Returns a {@link WebResourceLoader} which is able to load web resources + * from the webapp and all installed plugins. + * + * + * @return uber webresourceloader + */ + public UberWebResourceLoader getUberWebResourceLoader(); } diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginWrapper.java b/scm-core/src/main/java/sonia/scm/plugin/PluginWrapper.java index ba33f44273..46c3a4a980 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginWrapper.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginWrapper.java @@ -36,8 +36,8 @@ package sonia.scm.plugin; import java.nio.file.Path; /** - * Wrapper for a {@link Plugin}. The wrapper holds the directory and the - * {@link ClassLoader}, which is able to load plugin classes. + * Wrapper for a {@link Plugin}. The wrapper holds the directory, + * {@link ClassLoader} and {@link WebResourceLoader} of a plugin. * * @author Sebastian Sdorra * @since 2.0.0 @@ -50,12 +50,15 @@ public final class PluginWrapper * * @param plugin wrapped plugin * @param classLoader plugin class loader + * @param webResourceLoader web resource loader * @param directory plugin directory */ - public PluginWrapper(Plugin plugin, ClassLoader classLoader, Path directory) + public PluginWrapper(Plugin plugin, ClassLoader classLoader, + WebResourceLoader webResourceLoader, Path directory) { this.plugin = plugin; this.classLoader = classLoader; + this.webResourceLoader = webResourceLoader; this.directory = directory; } @@ -105,6 +108,17 @@ public final class PluginWrapper return plugin; } + /** + * Returns the {@link WebResourceLoader} for this plugin. + * + * + * @return web resource loader + */ + public WebResourceLoader getWebResourceLoader() + { + return webResourceLoader; + } + //~--- fields --------------------------------------------------------------- /** plugin class loader */ @@ -115,4 +129,7 @@ public final class PluginWrapper /** plugin */ private final Plugin plugin; + + /** plugin web resource loader */ + private final WebResourceLoader webResourceLoader; } diff --git a/scm-core/src/main/java/sonia/scm/plugin/UberWebResourceLoader.java b/scm-core/src/main/java/sonia/scm/plugin/UberWebResourceLoader.java new file mode 100644 index 0000000000..f480028511 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/UberWebResourceLoader.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.plugin; + +//~--- JDK imports ------------------------------------------------------------ + +import java.net.URL; + +import java.util.List; + +import javax.servlet.ServletContext; + +/** + * Load resources from {@link ServletContext} and from the installed plugins. + * The UberWebResourceLoader will first look into the {@link ServletContext} and + * afterwards it will search the plugin directories. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public interface UberWebResourceLoader extends WebResourceLoader +{ + + /** + * Returns all {@link URL} objects for the given path. The method will collect + * all resources from {@link ServletContext} and all plugin directories which + * matches the given path. The method will return an empty list, if no url + * could be found for the given path. + * + * @param path resource path + * + * @return list of url objects for the given path + */ + public List getResources(String path); +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java new file mode 100644 index 0000000000..94b31ac844 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/WebResourceLoader.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.plugin; + +//~--- JDK imports ------------------------------------------------------------ + +import java.net.URL; + +import javax.servlet.ServletContext; + +/** + * The WebResourceLoader is able to load web resources. The resources are loaded + * from a plugin webapp directory or from the {@link ServletContext}, according + * to its implementation. The {@link UberWebResourceLoader} is able to load + * resources from the {@link ServletContext} and all plugin directories. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public interface WebResourceLoader +{ + + /** + * Returns a {@link URL} for the given path. The method will return null if no + * resources could be found for the given path. + * + * @param path resource path + * + * @return url object for the given path or null + */ + public URL getResource(String path); +} diff --git a/scm-core/src/main/java/sonia/scm/resources/ResourceType.java b/scm-core/src/main/java/sonia/scm/resources/ResourceType.java index 0a78d13f08..faf3ac2a54 100644 --- a/scm-core/src/main/java/sonia/scm/resources/ResourceType.java +++ b/scm-core/src/main/java/sonia/scm/resources/ResourceType.java @@ -95,8 +95,8 @@ public enum ResourceType //~--- fields --------------------------------------------------------------- /** Field description */ - private String contentType; + private final String contentType; /** Field description */ - private String extension; + private final String extension; } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index 829ddcb6e7..7f02b1ac16 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -209,7 +209,8 @@ public class ScmContextListener extends GuiceServletContextListener */ private Injector getDefaultInjector(ServletContext servletCtx) { - DefaultPluginLoader pluginLoader = new DefaultPluginLoader(parent, plugins); + DefaultPluginLoader pluginLoader = new DefaultPluginLoader(servletCtx, + parent, plugins); ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader()); 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 cee470faae..030c9faaad 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java @@ -56,6 +56,8 @@ import java.util.Collection; import java.util.Enumeration; import java.util.Set; +import javax.servlet.ServletContext; + import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; @@ -81,13 +83,17 @@ public class DefaultPluginLoader implements PluginLoader /** * Constructs ... * + * @param servletContext * @param parent - * @param wrappedPlugins + * @param installedPlugins */ - public DefaultPluginLoader(ClassLoader parent, - Set wrappedPlugins) + public DefaultPluginLoader(ServletContext servletContext, ClassLoader parent, + Set installedPlugins) { - this.uberClassLoader = new UberClassLoader(parent, wrappedPlugins); + this.installedPlugins = installedPlugins; + this.uberClassLoader = new UberClassLoader(parent, installedPlugins); + this.uberWebResourceLoader = + new DefaultUberWebResourceLoader(servletContext, installedPlugins); try { @@ -96,16 +102,8 @@ public class DefaultPluginLoader implements PluginLoader modules = getInstalled(parent, context, PATH_MODULECONFIG); - // hidden plugins ??? - Set ips = getInstalled(parent, context, PATH_PLUGINCONFIG); - Builder builder = ImmutableSet.builder(); - - builder.addAll(ips); - builder.addAll(PluginsInternal.unwrap(wrappedPlugins)); - plugins = builder.build(); - appendExtensions(multiple, single, extensions, modules); - appendExtensions(multiple, single, extensions, plugins); + appendExtensions(multiple, single, extensions, unwrap()); } catch (IOException | JAXBException ex) { @@ -127,7 +125,7 @@ public class DefaultPluginLoader implements PluginLoader logger.info("start processing extensions"); appendExtensions(multiple, single, extensions, modules); - appendExtensions(multiple, single, extensions, plugins); + appendExtensions(multiple, single, extensions, unwrap()); if (logger.isInfoEnabled()) { @@ -173,9 +171,9 @@ public class DefaultPluginLoader implements PluginLoader * @return */ @Override - public Collection getInstalledPlugins() + public Collection getInstalledPlugins() { - return plugins; + return installedPlugins; } /** @@ -190,6 +188,18 @@ public class DefaultPluginLoader implements PluginLoader return uberClassLoader; } + /** + * Method description + * + * + * @return + */ + @Override + public UberWebResourceLoader getUberWebResourceLoader() + { + return uberWebResourceLoader; + } + //~--- methods -------------------------------------------------------------- /** @@ -242,6 +252,17 @@ public class DefaultPluginLoader implements PluginLoader } } + /** + * Method description + * + * + * @return + */ + private Iterable unwrap() + { + return PluginsInternal.unwrap(installedPlugins); + } + //~--- get methods ---------------------------------------------------------- /** @@ -282,15 +303,18 @@ public class DefaultPluginLoader implements PluginLoader /** Field description */ private final ClassLoader uberClassLoader; + /** Field description */ + private final UberWebResourceLoader uberWebResourceLoader; + + /** Field description */ + private Set installedPlugins; + /** Field description */ private Set modules; /** Field description */ private Set multiple = Sets.newHashSet(); - /** Field description */ - private Set plugins; - /** Field description */ private Set single = Sets.newHashSet(); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 5da9070558..4041910359 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -98,10 +98,6 @@ public class DefaultPluginManager implements PluginManager /** Field description */ public static final String ENCODING = "UTF-8"; - /** Field description */ - private static final String ADVANCED_CONFIGURATION = - "advanced-configuration.xml"; - /** the logger for DefaultPluginManager */ private static final Logger logger = LoggerFactory.getLogger(DefaultPluginManager.class); @@ -133,10 +129,11 @@ public class DefaultPluginManager implements PluginManager this.configuration = configuration; this.cache = cacheManager.getCache(CACHE_NAME); this.clientProvider = clientProvider; - installedPlugins = new HashMap(); + installedPlugins = new HashMap<>(); - for (Plugin plugin : pluginLoader.getInstalledPlugins()) + for (PluginWrapper wrapper : pluginLoader.getInstalledPlugins()) { + Plugin plugin = wrapper.getPlugin(); PluginInformation info = plugin.getInformation(); if ((info != null) && info.isValid()) @@ -247,15 +244,17 @@ public class DefaultPluginManager implements PluginManager throw new PluginConditionFailedException(condition); } - /*AetherPluginHandler aph = new AetherPluginHandler(this, context, - configuration); - Collection repositories = - Sets.newHashSet(new PluginRepository("package-repository", - "file://".concat(tempDirectory.getAbsolutePath()))); - - aph.setPluginRepositories(repositories); - - aph.install(plugin.getInformation().getId());*/ + /* + * AetherPluginHandler aph = new AetherPluginHandler(this, context, + * configuration); + * Collection repositories = + * Sets.newHashSet(new PluginRepository("package-repository", + * "file://".concat(tempDirectory.getAbsolutePath()))); + * + * aph.setPluginRepositories(repositories); + * + * aph.install(plugin.getInformation().getId()); + */ plugin.getInformation().setState(PluginState.INSTALLED); installedPlugins.put(plugin.getInformation().getId(), plugin); @@ -300,12 +299,14 @@ public class DefaultPluginManager implements PluginManager throw new PluginNotInstalledException(id.concat(" is not install")); } - /*if (pluginHandler == null) - { - getPluginCenter(); - } - - pluginHandler.uninstall(id);*/ + /* + * if (pluginHandler == null) + * { + * getPluginCenter(); + * } + * + * pluginHandler.uninstall(id); + */ installedPlugins.remove(id); preparePlugins(getPluginCenter()); } @@ -394,7 +395,7 @@ public class DefaultPluginManager implements PluginManager AssertUtil.assertIsNotNull(predicate); SecurityUtil.assertIsAdmin(); - Set infoSet = new HashSet(); + Set infoSet = new HashSet<>(); filter(infoSet, getInstalled(), predicate); filter(infoSet, getPluginCenter().getPlugins(), predicate); @@ -431,7 +432,7 @@ public class DefaultPluginManager implements PluginManager { SecurityUtil.assertIsAdmin(); - Set availablePlugins = new HashSet(); + Set availablePlugins = new HashSet<>(); Set centerPlugins = getPluginCenter().getPlugins(); for (PluginInformation info : centerPlugins) @@ -470,7 +471,7 @@ public class DefaultPluginManager implements PluginManager { SecurityUtil.assertIsAdmin(); - Set infoSet = new LinkedHashSet(); + Set infoSet = new LinkedHashSet<>(); for (Plugin plugin : installedPlugins.values()) { @@ -642,16 +643,18 @@ public class DefaultPluginManager implements PluginManager preparePlugins(center); cache.put(PluginCenter.class.getName(), center); - /*if (pluginHandler == null) - { - pluginHandler = new AetherPluginHandler(this, - SCMContext.getContext(), configuration, - advancedPluginConfiguration); - } - - pluginHandler.setPluginRepositories(center.getRepositories());*/ + /* + * if (pluginHandler == null) + * { + * pluginHandler = new AetherPluginHandler(this, + * SCMContext.getContext(), configuration, + * advancedPluginConfiguration); + * } + * + * pluginHandler.setPluginRepositories(center.getRepositories()); + */ } - catch (Exception ex) + catch (IOException | JAXBException ex) { logger.error("could not load plugins from plugin center", ex); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultUberWebResourceLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultUberWebResourceLoader.java new file mode 100644 index 0000000000..3bc39edb98 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultUberWebResourceLoader.java @@ -0,0 +1,231 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.plugin; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import java.net.MalformedURLException; +import java.net.URL; + +import java.util.List; + +import javax.servlet.ServletContext; + +/** + * Default implementation of the {@link UberWebResourceLoader}. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public class DefaultUberWebResourceLoader implements UberWebResourceLoader +{ + + /** + * the logger for DefaultUberWebResourceLoader + */ + private static final Logger logger = + LoggerFactory.getLogger(DefaultUberWebResourceLoader.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param servletContext + * @param plugins + */ + public DefaultUberWebResourceLoader(ServletContext servletContext, + Iterable plugins) + { + this.servletContext = servletContext; + this.plugins = plugins; + this.cache = CacheBuilder.newBuilder().build(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param path + * + * @return + */ + @Override + public URL getResource(String path) + { + URL resource = cache.getIfPresent(path); + + if (resource == null) + { + resource = find(path); + + if (resource != null) + { + cache.put(path, resource); + } + } + else + { + logger.trace("return web resource {} from cache", path); + } + + return resource; + } + + /** + * Method description + * + * + * @param path + * + * @return + */ + @Override + public List getResources(String path) + { + + // caching ??? + Builder resources = ImmutableList.builder(); + + try + { + URL ctxResource = servletContext.getResource(path); + + if (ctxResource != null) + { + logger.trace("found path {} at ServletContext", path); + resources.add(ctxResource); + } + + for (PluginWrapper wrapper : plugins) + { + URL resource = wrapper.getWebResourceLoader().getResource(path); + + if (resource != null) + { + resources.add(resource); + } + } + } + catch (MalformedURLException ex) + { + + // TODO define an extra exception + throw Throwables.propagate(ex); + } + + return resources.build(); + } + + /** + * Method description + * + * + * @return + */ + @VisibleForTesting + Cache getCache() + { + return cache; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param path + * + * @return + */ + private URL find(String path) + { + URL resource = null; + + try + { + resource = servletContext.getResource(path); + + if (resource == null) + { + for (PluginWrapper wrapper : plugins) + { + resource = wrapper.getWebResourceLoader().getResource(path); + + if (resource != null) + { + break; + } + } + } + else + { + logger.trace("found path {} at ServletContext", path); + } + } + catch (MalformedURLException ex) + { + + // TODO define an extra exception + throw Throwables.propagate(ex); + } + + return resource; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final Cache cache; + + /** Field description */ + private final Iterable plugins; + + /** Field description */ + private final ServletContext servletContext; +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PathWebResourceLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/PathWebResourceLoader.java new file mode 100644 index 0000000000..f6519d2d05 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PathWebResourceLoader.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.plugin; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; + +import java.net.MalformedURLException; +import java.net.URL; + +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Load web resources from a plugin webapp directory. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public class PathWebResourceLoader implements WebResourceLoader +{ + + /** Field description */ + private static final String DEFAULT_SEPARATOR = "/"; + + /** + * the logger for PathWebResourceLoader + */ + private static final Logger logger = + LoggerFactory.getLogger(PathWebResourceLoader.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param directory + */ + public PathWebResourceLoader(Path directory) + { + this.directory = directory; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param path + * + * @return + */ + @Override + public URL getResource(String path) + { + URL resource = null; + Path file = directory.resolve(filePath(path)); + + if (Files.exists(file)) + { + logger.trace("found path {} at {}", path, file); + + try + { + resource = file.toUri().toURL(); + } + catch (MalformedURLException ex) + { + logger.error("could not transform path to url", ex); + } + } + + return resource; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param path + * + * @return + */ + private String filePath(String path) + { + + // TODO handle illegal path parts, such as .. + String filePath = filePath(DEFAULT_SEPARATOR, path); + + if (!DEFAULT_SEPARATOR.equals(File.separator)) + { + filePath = filePath(File.separator, path); + } + + return filePath; + } + + /** + * Method description + * + * + * @param separator + * @param path + * + * @return + */ + private String filePath(String separator, String path) + { + String filePath = path; + + if (filePath.startsWith(separator)) + { + filePath = filePath.substring(separator.length()); + } + + return filePath; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final Path directory; +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java index 58de69d9ab..2694998e02 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java @@ -491,7 +491,8 @@ public final class PluginProcessor throws IOException { PluginWrapper wrapper = null; - Path descriptor = smp.getPath().resolve(PluginConstants.FILE_DESCRIPTOR); + Path directory = smp.getPath(); + Path descriptor = directory.resolve(PluginConstants.FILE_DESCRIPTOR); if (Files.exists(descriptor)) { @@ -499,7 +500,8 @@ public final class PluginProcessor Plugin plugin = createPlugin(cl, descriptor); - wrapper = new PluginWrapper(plugin, cl, smp.getPath()); + wrapper = new PluginWrapper(plugin, cl, + new PathWebResourceLoader(directory), directory); } else { diff --git a/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java b/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java index 4890e4b983..62bd145d37 100644 --- a/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java +++ b/scm-webapp/src/main/java/sonia/scm/resources/AbstractResourceManager.java @@ -51,6 +51,7 @@ import java.util.Map.Entry; import java.util.Set; import javax.servlet.ServletContext; +import sonia.scm.plugin.PluginWrapper; /** * @@ -117,7 +118,7 @@ public abstract class AbstractResourceManager implements ResourceManager @Override public List getResources(ResourceType type) { - List resources = new ArrayList(); + List resources = new ArrayList<>(); for (Entry e : resourceMap.entrySet()) { @@ -140,14 +141,14 @@ public abstract class AbstractResourceManager implements ResourceManager */ protected List getScriptResources() { - List resources = new ArrayList(); - Collection plugins = pluginLoader.getInstalledPlugins(); + List resources = new ArrayList<>(); + Collection wrappers = pluginLoader.getInstalledPlugins(); - if (plugins != null) + if (wrappers != null) { - for (Plugin plugin : plugins) + for (PluginWrapper plugin : wrappers) { - processPlugin(resources, plugin); + processPlugin(resources, plugin.getPlugin()); } } @@ -235,12 +236,7 @@ public abstract class AbstractResourceManager implements ResourceManager return false; } - if (this.type != other.type) - { - return false; - } - - return true; + return this.type == other.type; } /** @@ -291,10 +287,10 @@ public abstract class AbstractResourceManager implements ResourceManager //~--- fields ------------------------------------------------------------- /** Field description */ - private String name; + private final String name; /** Field description */ - private ResourceType type; + private final ResourceType type; } @@ -307,8 +303,7 @@ public abstract class AbstractResourceManager implements ResourceManager protected Set resourceHandlers; /** Field description */ - protected Map resourceMap = new HashMap(); + protected Map resourceMap = new HashMap<>(); /** Field description */ protected ServletContext servletContext; diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultUberWebResourceLoaderTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultUberWebResourceLoaderTest.java new file mode 100644 index 0000000000..19db037a9a --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultUberWebResourceLoaderTest.java @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.plugin; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.collect.Lists; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; + +import java.net.MalformedURLException; +import java.net.URL; + +import java.nio.file.Path; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase +{ + + /** Field description */ + private static URL BITBUCKET; + + /** Field description */ + private static URL GITHUB; + + /** Field description */ + private static URL SCM_MANAGER; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @throws MalformedURLException + */ + @BeforeClass + public static void prepare() throws MalformedURLException + { + SCM_MANAGER = new URL("https://www.scm-manager.org"); + BITBUCKET = new URL("https://bitbucket.org"); + GITHUB = new URL("https://github.com"); + } + + /** + * Method description + * + * + * @throws MalformedURLException + */ + @Test + public void testGetResourceFromCache() throws MalformedURLException + { + when(servletContext.getResource("/myresource")).thenReturn(SCM_MANAGER); + + DefaultUberWebResourceLoader resourceLoader = + new DefaultUberWebResourceLoader(servletContext, + new ArrayList()); + + resourceLoader.getCache().put("/myresource", GITHUB); + + URL resource = resourceLoader.getResource("/myresource"); + + assertSame(GITHUB, resource); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testGetResourceFromDirectory() throws IOException + { + File directory = temp.newFolder(); + File file = file(directory, "myresource"); + PluginWrapper wrapper = createPluginWrapper(directory); + List plugins = Lists.newArrayList(wrapper); + WebResourceLoader resourceLoader = + new DefaultUberWebResourceLoader(servletContext, plugins); + + assertEquals(file.toURI().toURL(), + resourceLoader.getResource("/myresource")); + } + + /** + * Method description + * + * @throws MalformedURLException + */ + @Test + public void testGetResourceFromServletContext() throws MalformedURLException + { + when(servletContext.getResource("/myresource")).thenReturn(SCM_MANAGER); + + WebResourceLoader resourceLoader = + new DefaultUberWebResourceLoader(servletContext, + new ArrayList()); + URL resource = resourceLoader.getResource("/myresource"); + + assertSame(SCM_MANAGER, resource); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testGetResources() throws IOException + { + when(servletContext.getResource("/myresource")).thenReturn(BITBUCKET); + + File directory = temp.newFolder(); + File file = file(directory, "myresource"); + PluginWrapper wrapper = createPluginWrapper(directory); + List plugins = Lists.newArrayList(wrapper); + + UberWebResourceLoader resourceLoader = + new DefaultUberWebResourceLoader(servletContext, plugins); + + assertThat(resourceLoader.getResources("/myresource"), + containsInAnyOrder(file.toURI().toURL(), BITBUCKET)); + } + + /** + * Method description + * + * + * @param directory + * + * @return + */ + private PluginWrapper createPluginWrapper(File directory) + { + return createPluginWrapper(directory.toPath()); + } + + /** + * Method description + * + * + * @param directory + * + * @return + */ + private PluginWrapper createPluginWrapper(Path directory) + { + return new PluginWrapper(null, null, new PathWebResourceLoader(directory), + directory); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Mock + private ServletContext servletContext; +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PathWebResourceLoaderTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PathWebResourceLoaderTest.java new file mode 100644 index 0000000000..571bf7537c --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PathWebResourceLoaderTest.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.plugin; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Test; + +import static org.junit.Assert.*; + + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; + +import java.net.URL; + +/** + * + * @author Sebastian Sdorra + */ +public class PathWebResourceLoaderTest extends WebResourceLoaderTestBase +{ + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testGetResource() throws IOException + { + File directory = temp.newFolder(); + URL url = file(directory, "myresource").toURI().toURL(); + + WebResourceLoader resourceLoader = + new PathWebResourceLoader(directory.toPath()); + + assertEquals(url, resourceLoader.getResource("/myresource")); + assertEquals(url, resourceLoader.getResource("myresource")); + assertNull(resourceLoader.getResource("other")); + } + + + //~--- fields --------------------------------------------------------------- + +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/WebResourceLoaderTestBase.java b/scm-webapp/src/test/java/sonia/scm/plugin/WebResourceLoaderTestBase.java new file mode 100644 index 0000000000..1047a067cf --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/WebResourceLoaderTestBase.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.plugin; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.io.Files; + +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import sonia.scm.util.IOUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class WebResourceLoaderTestBase +{ + + /** + * Method description + * + * + * @param directory + * @param path + * + * @return + * + * @throws IOException + */ + protected File file(File directory, String path) throws IOException + { + IOUtil.mkdirs(directory); + + File file = new File(directory, path); + + Files.append("a", file, Charsets.UTF_8); + + return file; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Rule + public TemporaryFolder temp = new TemporaryFolder(); +}