From 3244e552a9d9acb6338530848e49c80ac2b65603 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 8 Jan 2020 14:22:14 +0100 Subject: [PATCH 01/10] extension binder logs now to debug instead of info --- .../sonia/scm/plugin/ExtensionBinder.java | 130 +++--------------- 1 file changed, 20 insertions(+), 110 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java index 935bcbb3b6..a6e4c3281c 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionBinder.java @@ -55,63 +55,37 @@ import sonia.scm.util.Util; public final class ExtensionBinder { - /** Field description */ private static final String TYPE_LOOSE_EXT = "loose extension"; - - /** Field description */ private static final String TYPE_REST_RESOURCE = "rest resource"; + private static final String AS_EAGER_SINGLETON = " as eager singleton"; /** * the logger for ExtensionBinder */ - private static final Logger logger = - LoggerFactory.getLogger(ExtensionBinder.class); + private static final Logger logger = LoggerFactory.getLogger(ExtensionBinder.class); - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param binder - */ public ExtensionBinder(Binder binder) { this.binder = binder; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param collector - */ public void bind(ExtensionCollector collector) { - logger.info("bind extensions to extension points"); + logger.debug("bind extensions to extension points"); for (ExtensionPointElement epe : collector.getExtensionPointElements()) { bindExtensionPoint(collector, epe); } - logger.info("bind loose extensions"); + logger.debug("bind loose extensions"); bindLooseExtensions(collector.getLooseExtensions()); - logger.info("bind rest providers"); + logger.debug("bind rest providers"); bindRestProviders(collector.getRestProviders()); - logger.info("bind rest resources"); + logger.debug("bind rest resources"); bindRestResource(collector.getRestResources()); } - /** - * Method description - * - * - * @param collector - * @param epe - */ private void bindExtensionPoint(ExtensionCollector collector, ExtensionPointElement epe) { @@ -142,12 +116,6 @@ public final class ExtensionBinder } } - /** - * Method description - * - * - * @param extensions - */ private void bindLooseExtensions(Iterable extensions) { for (Class extension : extensions) @@ -156,46 +124,27 @@ public final class ExtensionBinder } } - /** - * Method description - * - * - * - * @param found - * - * @param extensionPoint - * - * @param boundClasses - * @param extensionPointClass - * @param extensions - */ - private void bindMultiExtensionPoint(ExtensionPointElement extensionPoint, - Iterable extensions) + private void bindMultiExtensionPoint(ExtensionPointElement extensionPoint, Iterable extensions) { Class extensionPointClass = extensionPoint.getClazz(); - if (logger.isInfoEnabled()) - { - logger.info("create multibinder for {}", extensionPointClass.getName()); - } - - Multibinder multibinder = Multibinder.newSetBinder(binder, - extensionPointClass); + logger.debug("create multibinder for {}", extensionPointClass.getName()); + Multibinder multibinder = Multibinder.newSetBinder(binder, extensionPointClass); for (Class extensionClass : extensions) { boolean eagerSingleton = isEagerSingleton(extensionClass); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { String as = Util.EMPTY_STRING; if (eagerSingleton) { - as = " as eager singleton"; + as = AS_EAGER_SINGLETON; } - logger.info("bind {} to multibinder of {}{}", extensionClass.getName(), + logger.debug("bind {} to multibinder of {}{}", extensionClass.getName(), extensionPointClass.getName(), as); } @@ -208,27 +157,15 @@ public final class ExtensionBinder } } - /** - * Method description - * - * - * @param restProviders - */ private void bindRestProviders(Iterable restProviders) { for (Class restProvider : restProviders) { - logger.info("bind rest provider {}", restProvider); + logger.debug("bind rest provider {}", restProvider); binder.bind(restProvider).in(Singleton.class); } } - /** - * Method description - * - * - * @param restResources - */ private void bindRestResource(Iterable restResources) { for (Class restResource : restResources) @@ -237,31 +174,22 @@ public final class ExtensionBinder } } - /** - * Method description - * - * - * @param extensionPointClass - * - * @param extensionPoint - * @param extensionClass - */ private void bindSingleInstance(ExtensionPointElement extensionPoint, Class extensionClass) { Class extensionPointClass = extensionPoint.getClazz(); boolean eagerSingleton = isEagerSingleton(extensionClass); - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { String as = Util.EMPTY_STRING; if (eagerSingleton) { - as = " as eager singleton"; + as = AS_EAGER_SINGLETON; } - logger.info("bind {} to {}{}", extensionClass.getName(), + logger.debug("bind {} to {}{}", extensionClass.getName(), extensionPointClass.getName(), as); } @@ -274,13 +202,6 @@ public final class ExtensionBinder } } - /** - * Method description - * - * - * @param type - * @param extension - */ private void singleBind(String type, Class extension) { StringBuilder log = new StringBuilder(); @@ -291,30 +212,19 @@ public final class ExtensionBinder if (isEagerSingleton(extension)) { - log.append(" as eager singleton"); + log.append(AS_EAGER_SINGLETON); abb.asEagerSingleton(); } - logger.info(log.toString()); + if (logger.isDebugEnabled()) { + logger.debug(log.toString()); + } } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param extensionClass - * - * @return - */ private boolean isEagerSingleton(Class extensionClass) { return extensionClass.isAnnotationPresent(EagerSingleton.class); } - //~--- fields --------------------------------------------------------------- - - /** Field description */ private final Binder binder; } From c1aa4af6e0710c7d6b880225646e510d60b7cad0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 8 Jan 2020 14:27:11 +0100 Subject: [PATCH 02/10] implemented optional dependencies Plugin authors could now define optional dependencies to other plugins in their pom. Extensions which are using classes from optional dependencies must specify this with the "requires" attribute of the extension annotation. Extensions with "requires" attribute are not installed if one of the specified plugins, is not installed. --- docs/dtd/plugin/2.0.0-01.dtd | 7 +- .../main/java/sonia/scm/plugin/Extension.java | 13 ++- .../sonia/scm/plugin/ExtensionElement.java | 26 ++++++ .../scm/plugin/InstalledPluginDescriptor.java | 36 ++++++++- .../main/java/sonia/scm/plugin/ScmModule.java | 6 +- .../java/sonia/scm/plugin/ScmModuleTest.java | 23 +++--- .../resources/sonia/scm/plugin/module.xml | 18 +++-- .../sonia/scm/plugin/DefaultPluginLoader.java | 15 +--- .../java/sonia/scm/plugin/ExplodedSmp.java | 4 +- .../sonia/scm/plugin/ExtensionCollector.java | 78 ++++++++++++------ .../sonia/scm/plugin/PluginProcessor.java | 2 +- .../java/sonia/scm/plugin/PluginTree.java | 37 +++++++-- .../sonia/scm/plugin/ExplodedSmpTest.java | 2 +- .../java/sonia/scm/plugin/PluginTreeTest.java | 81 +++++++++++++++++-- 14 files changed, 262 insertions(+), 86 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/plugin/ExtensionElement.java diff --git a/docs/dtd/plugin/2.0.0-01.dtd b/docs/dtd/plugin/2.0.0-01.dtd index 954a2c2219..62907f9481 100644 --- a/docs/dtd/plugin/2.0.0-01.dtd +++ b/docs/dtd/plugin/2.0.0-01.dtd @@ -23,7 +23,7 @@ --> - + @@ -79,7 +79,10 @@ - + + + + diff --git a/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java b/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java index ce3bd24406..948dd46200 100644 --- a/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java +++ b/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java @@ -49,4 +49,15 @@ import java.lang.annotation.Target; @Target({ ElementType.TYPE }) @PluginAnnotation("extension") @Retention(RetentionPolicy.RUNTIME) -public @interface Extension {} +public @interface Extension { + /** + * List of required plugins to load this extension. + * The requires attribute can be used to implement optional extensions. + * A plugin author is able to implement an extension point of an optional plugin and the extension is only loaded if + * all of the specified plugins are installed. + * + * @since 2.0.0 + * @return list of required plugins to load this extension + */ + String[] requires() default {}; +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/ExtensionElement.java b/scm-core/src/main/java/sonia/scm/plugin/ExtensionElement.java new file mode 100644 index 0000000000..1dd6f51291 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/ExtensionElement.java @@ -0,0 +1,26 @@ +package sonia.scm.plugin; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import java.util.HashSet; +import java.util.Set; + +@Getter +@ToString +@NoArgsConstructor +@EqualsAndHashCode +@AllArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class ExtensionElement { + @XmlElement(name = "class") + private String clazz; + private String description; + private Set requires = new HashSet<>(); +} diff --git a/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java b/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java index 88ae4c5dff..097a06ab2c 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/plugin/InstalledPluginDescriptor.java @@ -38,6 +38,7 @@ package sonia.scm.plugin; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -76,7 +77,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin */ public InstalledPluginDescriptor(int scmVersion, PluginInformation information, PluginResources resources, PluginCondition condition, - boolean childFirstClassLoader, Set dependencies) + boolean childFirstClassLoader, Set dependencies, Set optionalDependencies) { this.scmVersion = scmVersion; this.information = information; @@ -84,6 +85,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin this.condition = condition; this.childFirstClassLoader = childFirstClassLoader; this.dependencies = dependencies; + this.optionalDependencies = optionalDependencies; } //~--- methods -------------------------------------------------------------- @@ -116,7 +118,8 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin && Objects.equal(information, other.information) && Objects.equal(resources, other.resources) && Objects.equal(childFirstClassLoader, other.childFirstClassLoader) - && Objects.equal(dependencies, other.dependencies); + && Objects.equal(dependencies, other.dependencies) + && Objects.equal(optionalDependencies, other.optionalDependencies); } /** @@ -129,7 +132,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin public int hashCode() { return Objects.hashCode(scmVersion, condition, information, resources, - childFirstClassLoader, dependencies); + childFirstClassLoader, dependencies, optionalDependencies); } /** @@ -149,6 +152,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin .add("resources", resources) .add("childFirstClassLoader", childFirstClassLoader) .add("dependencies", dependencies) + .add("optionalDependencies", optionalDependencies) .toString(); //J+ } @@ -186,6 +190,27 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin return dependencies; } + /** + * Method description + * + * + * @return + * + * @since 2.0.0 + */ + public Set getOptionalDependencies() { + if (optionalDependencies == null) + { + optionalDependencies = ImmutableSet.of(); + } + + return optionalDependencies; + } + + public Set getDependenciesInclusiveOptionals() { + return ImmutableSet.copyOf(Iterables.concat(getDependencies(), getOptionalDependencies())); + } + /** * Method description * @@ -246,6 +271,11 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin @XmlElementWrapper(name = "dependencies") private Set dependencies; + /** Field description */ + @XmlElement(name = "dependency") + @XmlElementWrapper(name = "optional-dependencies") + private Set optionalDependencies; + /** Field description */ @XmlElement(name = "information") private PluginInformation information; diff --git a/scm-core/src/main/java/sonia/scm/plugin/ScmModule.java b/scm-core/src/main/java/sonia/scm/plugin/ScmModule.java index 683b2a7e0c..398698273c 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ScmModule.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ScmModule.java @@ -89,9 +89,9 @@ public class ScmModule * * @return */ - public Iterable> getExtensions() + public Iterable getExtensions() { - return unwrap(extensions); + return nonNull(extensions); } /** @@ -223,7 +223,7 @@ public class ScmModule /** Field description */ @XmlElement(name = "extension") - private Set extensions; + private Set extensions; /** Field description */ @XmlElement(name = "rest-provider") diff --git a/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java b/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java index 1629562132..86bd7fe19d 100644 --- a/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java +++ b/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java @@ -33,6 +33,7 @@ package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.collect.Iterables; import com.google.common.io.Resources; import org.junit.Test; @@ -69,43 +70,43 @@ public class ScmModuleTest //J- assertThat( - module.getExtensions(), + Iterables.transform(module.getExtensions(), ExtensionElement::getClazz), containsInAnyOrder( - String.class, - Integer.class + String.class.getName(), + Integer.class.getName() ) ); assertThat( - module.getExtensionPoints(), + module.getExtensionPoints(), containsInAnyOrder( - new ExtensionPointElement(String.class, "ext01", true, true), - new ExtensionPointElement(Long.class, "ext02", true, true), + new ExtensionPointElement(String.class, "ext01", true, true), + new ExtensionPointElement(Long.class, "ext02", true, true), new ExtensionPointElement(Integer.class, "ext03", false, true) ) ); assertThat( - module.getEvents(), + module.getEvents(), containsInAnyOrder( String.class, Boolean.class ) ); assertThat( - module.getSubscribers(), + module.getSubscribers(), containsInAnyOrder( - new SubscriberElement(Long.class, Integer.class, "sub01"), + new SubscriberElement(Long.class, Integer.class, "sub01"), new SubscriberElement(Double.class, Float.class, "sub02") ) ); assertThat( - module.getRestProviders(), + module.getRestProviders(), containsInAnyOrder( Integer.class, Long.class ) ); assertThat( - module.getRestResources(), + module.getRestResources(), containsInAnyOrder( Float.class, Double.class diff --git a/scm-core/src/test/resources/sonia/scm/plugin/module.xml b/scm-core/src/test/resources/sonia/scm/plugin/module.xml index c135803a7d..0bea44d06e 100644 --- a/scm-core/src/test/resources/sonia/scm/plugin/module.xml +++ b/scm-core/src/test/resources/sonia/scm/plugin/module.xml @@ -1,6 +1,6 @@ - + java.lang.Long @@ -47,28 +47,32 @@ java.lang.String + scm-mail-plugin + scm-review-plugin + ext01 java.lang.Integer + ext02 - + - + java.lang.Integer - + java.lang.Long - + - + java.lang.Float - + java.lang.Double 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 5612b0395c..e5ab89821f 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java @@ -99,7 +99,7 @@ public class DefaultPluginLoader implements PluginLoader modules = getInstalled(parent, context, PATH_MODULECONFIG); - collector = new ExtensionCollector(Iterables.concat(modules, unwrap())); + collector = new ExtensionCollector(parent, modules, installedPlugins); extensionProcessor = new DefaultExtensionProcessor(collector); } catch (IOException | JAXBException ex) @@ -170,19 +170,6 @@ public class DefaultPluginLoader implements PluginLoader return uberWebResourceLoader; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - private Iterable unwrap() - { - return PluginsInternal.unwrap(installedPlugins); - } - //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java index ee2514dc3e..60bcc09dcb 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java @@ -98,8 +98,8 @@ public final class ExplodedSmp implements Comparable { int result; - Set depends = plugin.getDependencies(); - Set odepends = o.plugin.getDependencies(); + Set depends = plugin.getDependenciesInclusiveOptionals(); + Set odepends = o.plugin.getDependenciesInclusiveOptionals(); if (depends.isEmpty() && odepends.isEmpty()) { diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java index 9ecf9a9da8..8ddf847935 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java @@ -39,6 +39,8 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; //~--- JDK imports ------------------------------------------------------------ @@ -47,6 +49,7 @@ import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; /** * @@ -56,23 +59,33 @@ import java.util.Set; public final class ExtensionCollector { - /** - * Constructs ... - * - * - * @param modules - */ - ExtensionCollector(Iterable modules) - { - for (ScmModule module : modules) - { + private static final Logger LOG = LoggerFactory.getLogger(ExtensionCollector.class); + + private final Set pluginIndex; + + public ExtensionCollector(ClassLoader moduleClassLoader, Set modules, Set installedPlugins) { + this.pluginIndex = createPluginIndex(installedPlugins); + + for (ScmModule module : modules) { collectRootElements(module); } - - for (ScmModule module : modules) - { - collectExtensions(module); + for (ScmModule plugin : PluginsInternal.unwrap(installedPlugins)) { + collectRootElements(plugin); } + + for (ScmModule module : modules) { + collectExtensions(moduleClassLoader, module); + } + + for (InstalledPlugin plugin : installedPlugins) { + collectExtensions(plugin.getClassLoader(), plugin.getDescriptor()); + } + } + + private Set createPluginIndex(Set installedPlugins) { + return installedPlugins.stream() + .map(p -> p.getDescriptor().getInformation().getName()) + .collect(Collectors.toSet()); } //~--- methods -------------------------------------------------------------- @@ -245,20 +258,35 @@ public final class ExtensionCollector } } - /** - * Method description - * - * - * @param module - */ - private void collectExtensions(ScmModule module) - { - for (Class extension : module.getExtensions()) - { - appendExtension(extension); + private void collectExtensions(ClassLoader defaultClassLoader, ScmModule module) { + for (ExtensionElement extension : module.getExtensions()) { + if (isRequirementFulfilled(extension)) { + Class extensionClass = loadExtension(defaultClassLoader, extension); + appendExtension(extensionClass); + } } } + private Class loadExtension(ClassLoader classLoader, ExtensionElement extension) { + try { + return classLoader.loadClass(extension.getClazz()); + } catch (ClassNotFoundException ex) { + throw new RuntimeException("failed to load clazz", ex); + } + } + + private boolean isRequirementFulfilled(ExtensionElement extension) { + if (extension.getRequires() != null) { + for (String requiredPlugin : extension.getRequires()) { + if (!pluginIndex.contains(requiredPlugin)) { + LOG.debug("skip loading of extension {}, because the required plugin {} is not installed", extension.getClazz(), requiredPlugin); + return false; + } + } + } + return true; + } + /** * Method description * 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 2bbab3c650..431cb44e1b 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java @@ -189,7 +189,7 @@ public final class PluginProcessor PluginTree pluginTree = new PluginTree(smps); - logger.trace("build plugin tree: {}", pluginTree); + logger.info("install plugin tree:\n{}", pluginTree); List rootNodes = pluginTree.getRootNodes(); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginTree.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginTree.java index bd338c0741..2ebe06db02 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginTree.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginTree.java @@ -35,15 +35,13 @@ package sonia.scm.plugin; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//~--- JDK imports ------------------------------------------------------------ - import java.util.Arrays; import java.util.List; -import java.util.Set; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -104,9 +102,7 @@ public final class PluginTree if ((condition == null) || condition.isSupported()) { - Set dependencies = plugin.getDependencies(); - - if ((dependencies == null) || dependencies.isEmpty()) + if (plugin.getDependencies().isEmpty() && plugin.getOptionalDependencies().isEmpty()) { rootNodes.add(new PluginNode(smp)); } @@ -170,6 +166,20 @@ public final class PluginTree //J+ } } + + boolean rootNode = smp.getPlugin().getDependencies().isEmpty(); + for (String dependency : smp.getPlugin().getOptionalDependencies()) { + if (appendNode(rootNodes, child, dependency)) { + rootNode = false; + } else { + logger.info("optional dependency {} of {} is not installed", dependency, child.getId()); + } + } + + if (rootNode) { + logger.info("could not find optional dependencies of {}, append it as root node", child.getId()); + rootNodes.add(new PluginNode(smp)); + } } /** @@ -212,7 +222,18 @@ public final class PluginTree @Override public String toString() { - return "plugin tree: " + rootNodes.toString(); + StringBuilder buffer = new StringBuilder(); + for (PluginNode node : rootNodes) { + append(buffer, "", node); + } + return buffer.toString(); + } + + private void append(StringBuilder buffer, String indent, PluginNode node) { + buffer.append(indent).append("+- ").append(node.getId()).append("\n"); + for (PluginNode child : node.getChildren()) { + append(buffer, indent + " ", child); + } } //~--- fields --------------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java index 090d903f49..c4a4c0a0d3 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java @@ -134,7 +134,7 @@ public class ExplodedSmpTest info.setVersion(version); InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(2, info, null, null, false, - Sets.newSet(dependencies)); + Sets.newSet(dependencies), null); return new ExplodedSmp(null, plugin); } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java index 72c48d4d16..33a139aa5a 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java @@ -34,6 +34,7 @@ package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.junit.Rule; @@ -72,7 +73,7 @@ public class PluginTreeTest PluginCondition condition = new PluginCondition("999", new ArrayList(), "hit"); InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(2, createInfo("a", "1"), null, condition, - false, null); + false, null, null); ExplodedSmp smp = createSmp(plugin); new PluginTree(smp).getRootNodes(); @@ -115,7 +116,7 @@ public class PluginTreeTest public void testScmVersion() throws IOException { InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(1, createInfo("a", "1"), null, null, false, - null); + null, null); ExplodedSmp smp = createSmp(plugin); new PluginTree(smp).getRootNodes(); @@ -131,10 +132,10 @@ public class PluginTreeTest public void testSimpleDependencies() throws IOException { //J- - ExplodedSmp[] smps = new ExplodedSmp[] { + ExplodedSmp[] smps = new ExplodedSmp[] { createSmpWithDependency("a"), createSmpWithDependency("b", "a"), - createSmpWithDependency("c", "a", "b") + createSmpWithDependency("c", "a", "b") }; //J+ @@ -152,6 +153,61 @@ public class PluginTreeTest assertThat(unwrapIds(b.getChildren()), containsInAnyOrder("c")); } + @Test + public void testWithOptionalDependency() throws IOException { + ExplodedSmp[] smps = new ExplodedSmp[] { + createSmpWithDependency("a"), + createSmpWithDependency("b", null, ImmutableSet.of("a")), + createSmpWithDependency("c", null, ImmutableSet.of("a", "b")) + }; + + PluginTree tree = new PluginTree(smps); + List rootNodes = tree.getRootNodes(); + + assertThat(unwrapIds(rootNodes), containsInAnyOrder("a")); + + PluginNode a = rootNodes.get(0); + assertThat(unwrapIds(a.getChildren()), containsInAnyOrder("b", "c")); + + PluginNode b = a.getChild("b"); + assertThat(unwrapIds(b.getChildren()), containsInAnyOrder("c")); + } + + @Test + public void testWithDeepOptionalDependency() throws IOException { + ExplodedSmp[] smps = new ExplodedSmp[] { + createSmpWithDependency("a"), + createSmpWithDependency("b", "a"), + createSmpWithDependency("c", null, ImmutableSet.of("b")) + }; + + PluginTree tree = new PluginTree(smps); + + System.out.println(tree); + + List rootNodes = tree.getRootNodes(); + + assertThat(unwrapIds(rootNodes), containsInAnyOrder("a")); + + PluginNode a = rootNodes.get(0); + assertThat(unwrapIds(a.getChildren()), containsInAnyOrder("b")); + + PluginNode b = a.getChild("b"); + assertThat(unwrapIds(b.getChildren()), containsInAnyOrder("c")); + } + + @Test + public void testWithNonExistentOptionalDependency() throws IOException { + ExplodedSmp[] smps = new ExplodedSmp[] { + createSmpWithDependency("a", null, ImmutableSet.of("b")) + }; + + PluginTree tree = new PluginTree(smps); + List rootNodes = tree.getRootNodes(); + + assertThat(unwrapIds(rootNodes), containsInAnyOrder("a")); + } + /** * Method description * @@ -200,7 +256,7 @@ public class PluginTreeTest private ExplodedSmp createSmp(String name) throws IOException { return createSmp(new InstalledPluginDescriptor(2, createInfo(name, "1.0.0"), null, null, - false, null)); + false, null, null)); } /** @@ -224,10 +280,19 @@ public class PluginTreeTest { dependencySet.add(d); } + return createSmpWithDependency(name, dependencySet, null); + } - InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(2, createInfo(name, "1"), null, null, - false, dependencySet); - + private ExplodedSmp createSmpWithDependency(String name, Set dependencies, Set optionalDependencies) throws IOException { + InstalledPluginDescriptor plugin = new InstalledPluginDescriptor( + 2, + createInfo(name, "1"), + null, + null, + false, + dependencies, + optionalDependencies + ); return createSmp(plugin); } From 8236e6be7fdbb491f20826d58e52695837483d12 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 9 Jan 2020 08:19:13 +0100 Subject: [PATCH 03/10] added missing requires element to extension --- docs/dtd/plugin/2.0.0-01.dtd | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/dtd/plugin/2.0.0-01.dtd b/docs/dtd/plugin/2.0.0-01.dtd index 62907f9481..23441a5a63 100644 --- a/docs/dtd/plugin/2.0.0-01.dtd +++ b/docs/dtd/plugin/2.0.0-01.dtd @@ -88,7 +88,10 @@ - + + + + From bac3068b7387ac2dde77ca3fa55eb045f961330e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 9 Jan 2020 08:19:43 +0100 Subject: [PATCH 04/10] improve javadoc of requires attribute --- scm-annotations/src/main/java/sonia/scm/plugin/Extension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java b/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java index 948dd46200..2d99ed69be 100644 --- a/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java +++ b/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java @@ -51,7 +51,7 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface Extension { /** - * List of required plugins to load this extension. + * This extension is loaded only if all of the specified plugins are installed. * The requires attribute can be used to implement optional extensions. * A plugin author is able to implement an extension point of an optional plugin and the extension is only loaded if * all of the specified plugins are installed. From 6b62f0aab6f9f3e1d0212d6372952b40c55a74d2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 9 Jan 2020 08:20:47 +0100 Subject: [PATCH 05/10] throw more specific PluginLoadException instead of generic RuntimeException --- .../src/main/java/sonia/scm/plugin/ExtensionCollector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java index 8ddf847935..b4d4451596 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java @@ -271,7 +271,7 @@ public final class ExtensionCollector try { return classLoader.loadClass(extension.getClazz()); } catch (ClassNotFoundException ex) { - throw new RuntimeException("failed to load clazz", ex); + throw new PluginLoadException("failed to load clazz", ex); } } From c0e82b129981228d410bb3d3308fe197ced88348 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 9 Jan 2020 14:15:42 +0100 Subject: [PATCH 06/10] fixed mutliple versions of hibernate-validator SCM-WebApp had multiple copies of hibernate-validator 5.3.6 and 6.1.0. We use now only 6.1.0. --- pom.xml | 7 +++++++ scm-core/pom.xml | 13 +++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index a27dcdb44e..918d43439a 100644 --- a/pom.xml +++ b/pom.xml @@ -284,6 +284,12 @@ ${jackson.version} + + org.hibernate + hibernate-validator + ${hibernate-validator.version} + + @@ -843,6 +849,7 @@ 2.10.0 4.0 2.3.0 + 6.1.0.Final 1.6.1 diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 4e8e315253..086013364b 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -173,6 +173,13 @@ activation + + + + org.hibernate + hibernate-validator + + @@ -214,12 +221,6 @@ shiro-unit test - - org.hibernate - hibernate-validator - 5.3.6.Final - compile - From 0ff4e22d87ba94cf9c9eeae2c49ceb55febc6ea9 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 9 Jan 2020 14:10:18 +0000 Subject: [PATCH 07/10] Close branch feature/optional_dependencies From 917a2ba1d664774861b8d856f06b14ee5358d0ef Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 9 Jan 2020 16:31:28 +0100 Subject: [PATCH 08/10] Add story for diffs with binary files --- .../src/__resources__/Diff.binary.ts | 18 ++ .../src/__snapshots__/storyshots.test.ts.snap | 299 ++++++++++++++++++ .../ui-components/src/repos/Diff.stories.tsx | 5 + 3 files changed, 322 insertions(+) create mode 100644 scm-ui/ui-components/src/__resources__/Diff.binary.ts diff --git a/scm-ui/ui-components/src/__resources__/Diff.binary.ts b/scm-ui/ui-components/src/__resources__/Diff.binary.ts new file mode 100644 index 0000000000..d11d0fcc0e --- /dev/null +++ b/scm-ui/ui-components/src/__resources__/Diff.binary.ts @@ -0,0 +1,18 @@ +export default `diff --git a/Main.java b/Main.java +index 9b5ca13..7ced845 100644 +--- a/Main.java ++++ b/Main.java +@@ -1,5 +1,5 @@ + class Main { + - public static void main(String[] args) { + + public static void main(String[] arguments) { + System.out.println("Expect nothing more to happen."); + } +} +diff --git a/conflict.png b/conflict.png +new file mode 100644 +index 0000000..7c77c7f +--- /dev/null ++++ b/conflict.png +Binary files differ +`; diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 8034701cbd..27e3eb6da0 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -334,6 +334,305 @@ exports[`Storyshots DateFromNow Default 1`] = ` `; +exports[`Storyshots Diff Binaries 1`] = ` +Array [ +
+
+
+
+ + + Main.java + + + modify + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ 1 + + 1 + + class Main { +
+ 2 + + 2 + + - public static void main(String[] args) { +
+ 3 + + 3 + + + public static void main(String[] arguments) { +
+ 4 + + 4 + + System.out.println("Expect nothing more to happen."); +
+ 5 + + 5 + + } +
+
+
, +
+
+
+
+ + conflict.png + + + modify + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+
, +] +`; + exports[`Storyshots Diff Collapsed 1`] = ` Array [
{ const hunkDiffFiles = parser.parse(hunksDiff); return ; + }) + .add("Binaries", () => { + const binaryDiffFiles = parser.parse(binaryDiff); + return ; }); From 779a2004a05083b1f03e933cbcdb88f26b65fe82 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 9 Jan 2020 16:49:03 +0100 Subject: [PATCH 09/10] use new version of gitdiff-parser, to fix wrong type of binary diffs --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8d9a9c931b..c50258f18d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "resolutions": { "babel-core": "7.0.0-bridge.0", - "gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#6baa7278824ecd17a199d842ca720d0453f68982" + "gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#ed3fe7de73dbb0a06c3e6adbbdf22dbae6e66351" }, "babel": { "presets": [ diff --git a/yarn.lock b/yarn.lock index b69a934a48..f75ea838fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7332,9 +7332,9 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -gitdiff-parser@^0.1.2, "gitdiff-parser@https://github.com/scm-manager/gitdiff-parser#6baa7278824ecd17a199d842ca720d0453f68982": +gitdiff-parser@^0.1.2, "gitdiff-parser@https://github.com/scm-manager/gitdiff-parser#ed3fe7de73dbb0a06c3e6adbbdf22dbae6e66351": version "0.1.2" - resolved "https://github.com/scm-manager/gitdiff-parser#6baa7278824ecd17a199d842ca720d0453f68982" + resolved "https://github.com/scm-manager/gitdiff-parser#ed3fe7de73dbb0a06c3e6adbbdf22dbae6e66351" glob-parent@^3.1.0: version "3.1.0" From bbb2af7ebc4d59e6af8cfd9ab6ea74dd90558dc4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 9 Jan 2020 17:06:55 +0100 Subject: [PATCH 10/10] update storyshot snapshots --- scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 27e3eb6da0..71aec23d5a 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -577,7 +577,7 @@ Array [ - modify + add