diff --git a/docs/dtd/plugin/2.0.0-01.dtd b/docs/dtd/plugin/2.0.0-01.dtd index 954a2c2219..23441a5a63 100644 --- a/docs/dtd/plugin/2.0.0-01.dtd +++ b/docs/dtd/plugin/2.0.0-01.dtd @@ -23,7 +23,7 @@ --> - + @@ -79,13 +79,19 @@ - + + + + - + + + + 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/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-annotations/src/main/java/sonia/scm/plugin/Extension.java b/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java index ce3bd24406..2d99ed69be 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 { + /** + * 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. + * + * @since 2.0.0 + * @return list of required plugins to load this extension + */ + String[] requires() default {}; +} 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 - 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-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 99e9f85993..c7ab6a81b6 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 + + + add + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+
, +] +`; + exports[`Storyshots Diff Collapsed 1`] = ` Array [
{ const hunkDiffFiles = parser.parse(hunksDiff); return ; + }) + .add("Binaries", () => { + const binaryDiffFiles = parser.parse(binaryDiff); + return ; }); 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/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; } 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..b4d4451596 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 PluginLoadException("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); } 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"