diff --git a/CHANGELOG.md b/CHANGELOG.md index 36842f0851..4209598c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed +- Removed the `requires` attribute on the `@Extension` annotation and instead create a new `@Requires` annotation ([#1097](https://github.com/scm-manager/scm-manager/pull/1097)) + ## 2.0.0-rc7 - 2020-04-09 ### Added - Fire various plugin events ([#1088](https://github.com/scm-manager/scm-manager/pull/1088)) diff --git a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ClassSetElement.java b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ClassSetElement.java index 48c769a422..ffa6d9f16d 100644 --- a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ClassSetElement.java +++ b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ClassSetElement.java @@ -21,138 +21,84 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.annotation; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Strings; - import org.w3c.dom.Document; import org.w3c.dom.Element; -//~--- JDK imports ------------------------------------------------------------ - import java.util.Map; import java.util.Map.Entry; +//~--- JDK imports ------------------------------------------------------------ + /** - * * @author Sebastian Sdorra */ -public class ClassSetElement implements DescriptorElement -{ +public class ClassSetElement implements DescriptorElement { - /** Field description */ private static final String EL_CLASS = "class"; - - /** Field description */ private static final String EL_DESCRIPTION = "description"; - //~--- constructors --------------------------------------------------------- + private final String elementName; + private final Iterable classes; - /** - * Constructs ... - * - * - * @param elementName - * @param classes - */ - public ClassSetElement(String elementName, - Iterable classes) - { + public ClassSetElement(String elementName, Iterable classes) { this.elementName = elementName; this.classes = classes; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param doc - * @param root - */ @Override - public void append(Document doc, Element root) - { - - for (ClassWithAttributes c : classes) - { + public void append(Document doc, Element root) { + for (ClassWithAttributes c : classes) { Element element = doc.createElement(elementName); Element classEl = doc.createElement(EL_CLASS); classEl.setTextContent(c.className); - if (!Strings.isNullOrEmpty(c.description)) - { + if (!Strings.isNullOrEmpty(c.description)) { Element descriptionEl = doc.createElement(EL_DESCRIPTION); descriptionEl.setTextContent(c.description); element.appendChild(descriptionEl); } - for (Entry e : c.attributes.entrySet()) - { + for (Entry e : c.attributes.entrySet()) { Element attr = doc.createElement(e.getKey()); attr.setTextContent(e.getValue()); element.appendChild(attr); } + if (c.requires != null) { + for (String requiresEntry : c.requires) { + Element requiresElement = doc.createElement("requires"); + requiresElement.setTextContent(requiresEntry); + element.appendChild(requiresElement); + } + } + element.appendChild(classEl); root.appendChild(element); } - } - //~--- inner classes -------------------------------------------------------- + public static class ClassWithAttributes { - /** - * Class description - * - * - * @version Enter version here..., 14/03/18 - * @author Enter your name here... - */ - public static class ClassWithAttributes - { - - /** - * Constructs ... - * - * - * @param className - * @param description - * @param attributes - */ - public ClassWithAttributes(String className, String description, - Map attributes) - { - this.className = className; - this.description = description; - this.attributes = attributes; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ + private final String className; + private final String description; + private final String[] requires; private final Map attributes; - /** Field description */ - private final String className; - - /** Field description */ - private final String description; + public ClassWithAttributes(String className, String description, + String[] requires, Map attributes) { + this.className = className; + this.description = description; + this.requires = requires; + this.attributes = attributes; + } } - - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final Iterable classes; - - /** Field description */ - private final String elementName; } diff --git a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java index 7aa2135352..f800040bc6 100644 --- a/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java +++ b/scm-annotation-processor/src/main/java/sonia/scm/annotation/ScmAnnotationProcessor.java @@ -42,6 +42,7 @@ import org.xml.sax.SAXException; import sonia.scm.annotation.ClassSetElement.ClassWithAttributes; import sonia.scm.plugin.PluginAnnotation; +import sonia.scm.plugin.Requires; //~--- JDK imports ------------------------------------------------------------ @@ -247,6 +248,14 @@ public final class ScmAnnotationProcessor extends AbstractProcessor { if (isClassOrInterface(e)) { TypeElement type = (TypeElement) e; + + String[] requires = null; + Requires requiresAnnotation = type.getAnnotation(Requires.class); + + if (requiresAnnotation != null) { + requires = requiresAnnotation.value(); + } + String desc = processingEnv.getElementUtils().getDocComment(type); if (desc != null) { @@ -255,7 +264,7 @@ public final class ScmAnnotationProcessor extends AbstractProcessor { classes.add( new ClassWithAttributes( - type.getQualifiedName().toString(), desc, getAttributesFromAnnotation(e, annotation) + type.getQualifiedName().toString(), desc, requires, getAttributesFromAnnotation(e, annotation) ) ); } 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 3d3ff59ba0..7191780e69 100644 --- a/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java +++ b/scm-annotations/src/main/java/sonia/scm/plugin/Extension.java @@ -41,14 +41,4 @@ import java.lang.annotation.Target; @PluginAnnotation("extension") @Retention(RetentionPolicy.RUNTIME) 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/src/main/java/sonia/scm/plugin/ExtensionElement.java b/scm-annotations/src/main/java/sonia/scm/plugin/Requires.java similarity index 59% rename from scm-core/src/main/java/sonia/scm/plugin/ExtensionElement.java rename to scm-annotations/src/main/java/sonia/scm/plugin/Requires.java index d8698f1a6a..e70bf582b1 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ExtensionElement.java +++ b/scm-annotations/src/main/java/sonia/scm/plugin/Requires.java @@ -21,30 +21,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.plugin; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -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; +/** + * The {@link Requires} annotation can be used to bind classes (e.g. Extensions, Rest Resources, etc.) only if certain + * plugins is installed. This is very useful in combination with optional plugin dependencies. + * + * @since 2.0.0 + */ +@Documented +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Requires { -@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<>(); + /** + * The annotated class is loaded only if all of the specified plugins are installed. + * The value has to be an array of string with the plugin names the class should depend on. + * + * @return list of required plugins to load this class + */ + String[] value(); } diff --git a/scm-core/src/main/java/sonia/scm/plugin/ClassElement.java b/scm-core/src/main/java/sonia/scm/plugin/ClassElement.java index ecae44b30a..36855e932a 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ClassElement.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ClassElement.java @@ -21,133 +21,36 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; +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; - -//~--- JDK imports ------------------------------------------------------------ +import java.util.HashSet; +import java.util.Set; /** - * * @author Sebastian Sdorra * @since 2.0.0 */ -public final class ClassElement -{ - - /** - * Constructs ... - * - */ - ClassElement() {} - - /** - * Constructs ... - * - * - * @param clazz - * @param description - */ - public ClassElement(Class clazz, String description) - { - this.clazz = clazz; - this.description = description; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param obj - * - * @return - */ - @Override - public boolean equals(Object obj) - { - if (obj == null) - { - return false; - } - - if (getClass() != obj.getClass()) - { - return false; - } - - final ClassElement other = (ClassElement) obj; - - return Objects.equal(clazz, other.clazz) - && Objects.equal(description, other.description); - } - - /** - * Method description - * - * - * @return - */ - @Override - public int hashCode() - { - return Objects.hashCode(clazz, description); - } - - /** - * Method description - * - * - * @return - */ - @Override - public String toString() - { - //J- - return MoreObjects.toStringHelper(this) - .add("clazz", clazz) - .add("description", description) - .toString(); - //J+ - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public Class getClazz() - { - return clazz; - } - - /** - * Method description - * - * - * @return - */ - public String getDescription() - { - return description; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ +@Getter +@NoArgsConstructor +@AllArgsConstructor +@ToString +@EqualsAndHashCode +@XmlAccessorType(XmlAccessType.FIELD) +public final class ClassElement { @XmlElement(name = "class") - private Class clazz; - - /** Field description */ + private String clazz; private String description; + private Set requires = new HashSet<>(); } 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 32e522f905..71f8d24415 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ScmModule.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ScmModule.java @@ -48,10 +48,6 @@ import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.FIELD) public class ScmModule { - - /** Field description */ - private static final Unwrapper unwrapper = new Unwrapper(); - //~--- get methods ---------------------------------------------------------- /** @@ -60,9 +56,9 @@ public class ScmModule * * @return */ - public Iterable> getEvents() + public Iterable getEvents() { - return unwrap(events); + return nonNull(events); } /** @@ -82,7 +78,7 @@ public class ScmModule * * @return */ - public Iterable getExtensions() + public Iterable getExtensions() { return nonNull(extensions); } @@ -93,9 +89,9 @@ public class ScmModule * * @return */ - public Iterable> getRestProviders() + public Iterable getRestProviders() { - return unwrap(restProviders); + return nonNull(restProviders); } /** @@ -104,9 +100,9 @@ public class ScmModule * * @return */ - public Iterable> getRestResources() + public Iterable getRestResources() { - return unwrap(restResources); + return nonNull(restResources); } /** @@ -152,57 +148,6 @@ public class ScmModule return iterable; } - /** - * Method description - * - * - * @param iterable - * - * @return - */ - private Iterable> unwrap(Iterable iterable) - { - Iterable> unwrapped; - - if (iterable != null) - { - unwrapped = Iterables.transform(iterable, unwrapper); - } - else - { - unwrapped = ImmutableSet.of(); - } - - return unwrapped; - } - - //~--- inner classes -------------------------------------------------------- - - /** - * Class description - * - * - * @version Enter version here..., 14/03/28 - * @author Enter your name here... - */ - private static class Unwrapper implements Function> - { - - /** - * Method description - * - * - * @param classElement - * - * @return - */ - @Override - public Class apply(ClassElement classElement) - { - return classElement.getClazz(); - } - } - //~--- fields --------------------------------------------------------------- @@ -216,7 +161,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 4781d8e923..6ed7aac287 100644 --- a/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java +++ b/scm-core/src/test/java/sonia/scm/plugin/ScmModuleTest.java @@ -38,6 +38,7 @@ import static org.junit.Assert.*; //~--- JDK imports ------------------------------------------------------------ import java.net.URL; +import java.util.Collections; import javax.xml.bind.JAXB; @@ -63,7 +64,7 @@ public class ScmModuleTest //J- assertThat( - Iterables.transform(module.getExtensions(), ExtensionElement::getClazz), + Iterables.transform(module.getExtensions(), ClassElement::getClazz), containsInAnyOrder( String.class.getName(), Integer.class.getName() @@ -78,11 +79,8 @@ public class ScmModuleTest ) ); assertThat( - module.getEvents(), - containsInAnyOrder( - String.class, - Boolean.class - ) + module.getEvents().iterator().next(), + instanceOf(ClassElement.class) ); assertThat( module.getSubscribers(), @@ -92,18 +90,12 @@ public class ScmModuleTest ) ); assertThat( - module.getRestProviders(), - containsInAnyOrder( - Integer.class, - Long.class - ) + module.getRestProviders().iterator().next(), + instanceOf(ClassElement.class) ); assertThat( - module.getRestResources(), - containsInAnyOrder( - Float.class, - Double.class - ) + module.getRestResources().iterator().next(), + instanceOf(ClassElement.class) ); //J+ } 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 86cafae419..99a87f5814 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,7 @@ import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -60,10 +61,10 @@ public final class ExtensionCollector this.pluginIndex = createPluginIndex(installedPlugins); for (ScmModule module : modules) { - collectRootElements(module); + collectRootElements(moduleClassLoader, module); } - for (ScmModule plugin : PluginsInternal.unwrap(installedPlugins)) { - collectRootElements(plugin); + for (InstalledPlugin plugin : installedPlugins) { + collectRootElements(plugin.getClassLoader(), plugin.getDescriptor()); } for (ScmModule module : modules) { @@ -252,7 +253,7 @@ public final class ExtensionCollector } private void collectExtensions(ClassLoader defaultClassLoader, ScmModule module) { - for (ExtensionElement extension : module.getExtensions()) { + for (ClassElement extension : module.getExtensions()) { if (isRequirementFulfilled(extension)) { Class extensionClass = loadExtension(defaultClassLoader, extension); appendExtension(extensionClass); @@ -260,7 +261,18 @@ public final class ExtensionCollector } } - private Class loadExtension(ClassLoader classLoader, ExtensionElement extension) { + private Set> collectClasses(ClassLoader defaultClassLoader, Iterable classElements) { + Set> classes = new HashSet<>(); + for (ClassElement element : classElements) { + if (isRequirementFulfilled(element)) { + Class loadedClass = loadExtension(defaultClassLoader, element); + classes.add(loadedClass); + } + } + return classes; + } + + private Class loadExtension(ClassLoader classLoader, ClassElement extension) { try { return classLoader.loadClass(extension.getClazz()); } catch (ClassNotFoundException ex) { @@ -268,7 +280,7 @@ public final class ExtensionCollector } } - private boolean isRequirementFulfilled(ExtensionElement extension) { + private boolean isRequirementFulfilled(ClassElement extension) { if (extension.getRequires() != null) { for (String requiredPlugin : extension.getRequires()) { if (!pluginIndex.contains(requiredPlugin)) { @@ -286,15 +298,15 @@ public final class ExtensionCollector * * @param module */ - private void collectRootElements(ScmModule module) + private void collectRootElements(ClassLoader classLoader, ScmModule module) { for (ExtensionPointElement epe : module.getExtensionPoints()) { extensionPointIndex.put(epe.getClazz(), epe); } - restProviders.addAll(Lists.newArrayList(module.getRestProviders())); - restResources.addAll(Lists.newArrayList(module.getRestResources())); + restProviders.addAll(collectClasses(classLoader, module.getRestProviders())); + restResources.addAll(collectClasses(classLoader, module.getRestResources())); Iterables.addAll(webElements, module.getWebElements()); }