From 81e8dc428cb3ef55a6541fafbca1dd62f72932c7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 16 Apr 2020 11:57:38 +0200 Subject: [PATCH] support requires annotation on WebElements --- .../java/sonia/scm/plugin/ClassElement.java | 5 +- .../sonia/scm/plugin/ExtensionProcessor.java | 4 +- .../scm/plugin/WebElementDescriptor.java | 159 ++++-------------- .../sonia/scm/plugin/WebElementExtension.java | 43 +++++ .../scm/plugin/WebElementDescriptorTest.java | 77 +++++++++ .../sonia/scm/filter/WebElementCollector.java | 12 +- .../lifecycle/modules/ScmServletModule.java | 8 +- .../scm/plugin/DefaultExtensionProcessor.java | 4 +- .../sonia/scm/plugin/ExtensionCollector.java | 26 ++- 9 files changed, 188 insertions(+), 150 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/plugin/WebElementExtension.java create mode 100644 scm-core/src/test/java/sonia/scm/plugin/WebElementDescriptorTest.java 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 36855e932a..b439f80545 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ClassElement.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ClassElement.java @@ -26,6 +26,7 @@ package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -43,12 +44,12 @@ import java.util.Set; * @since 2.0.0 */ @Getter -@NoArgsConstructor @AllArgsConstructor @ToString @EqualsAndHashCode @XmlAccessorType(XmlAccessType.FIELD) -public final class ClassElement { +@NoArgsConstructor(access = AccessLevel.PACKAGE) +public class ClassElement { @XmlElement(name = "class") private String clazz; private String description; diff --git a/scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java b/scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java index 4901998109..0fe06f1764 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java +++ b/scm-core/src/main/java/sonia/scm/plugin/ExtensionProcessor.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- @@ -76,5 +76,5 @@ public interface ExtensionProcessor * * @return collected web elements */ - public Iterable getWebElements(); + public Iterable getWebElements(); } diff --git a/scm-core/src/main/java/sonia/scm/plugin/WebElementDescriptor.java b/scm-core/src/main/java/sonia/scm/plugin/WebElementDescriptor.java index e9f3cc1410..65651b1616 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/WebElementDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/plugin/WebElementDescriptor.java @@ -21,155 +21,60 @@ * 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 lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; import sonia.scm.xml.XmlArrayStringAdapter; -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Arrays; -import java.util.Objects; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Arrays; /** - * Descriptor for web elements such as filter or servlets. A web element can be registered by using the + * Descriptor for web elements such as filter or servlets. A web element can be registered by using the * {@link sonia.scm.filter.WebElement} annotation. - * + * * @author Sebastian Sdorra * @since 2.0.0 */ +@Getter +@AllArgsConstructor +@ToString +@EqualsAndHashCode @XmlRootElement(name = "web-element") @XmlAccessorType(XmlAccessType.FIELD) -public final class WebElementDescriptor -{ +@NoArgsConstructor(access = AccessLevel.PACKAGE) +public final class WebElementDescriptor extends ClassElement { - /** - * Constructs ... - * - */ - WebElementDescriptor() {} - - /** - * Constructs ... - * - * - * @param clazz - * @param pattern - * @param morePatterns - * @param regex - */ - public WebElementDescriptor(Class clazz, String pattern, - String[] morePatterns, boolean regex) - { - this.clazz = clazz; - this.pattern = pattern; - this.morePatterns = morePatterns; - this.regex = regex; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public Class getClazz() - { - return clazz; - } - - /** - * Method description - * - * - * @return - */ - public String[] getMorePatterns() - { - String[] patterns; - - if (morePatterns != null) - { - patterns = Arrays.copyOf(morePatterns, morePatterns.length); - } - else - { - patterns = new String[0]; - } - - return patterns; - } - - /** - * Method description - * - * - * @return - */ - public String getPattern() - { - return pattern; - } - - /** - * Method description - * - * - * @return - */ - public boolean isRegex() - { - return regex; - } - - @Override - public int hashCode() { - return Objects.hash(clazz, pattern, Arrays.hashCode(morePatterns), regex); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - - final WebElementDescriptor other = (WebElementDescriptor) obj; - return Objects.equals(clazz, other.clazz) - && Objects.equals(pattern, other.pattern) - && Arrays.equals(morePatterns, other.morePatterns) - && this.regex == other.regex; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - @XmlElement(name = "class") - private Class clazz; - - /** Field description */ - @XmlJavaTypeAdapter(XmlArrayStringAdapter.class) - private String[] morePatterns; - - /** Field description */ @XmlElement(name = "value") private String pattern; - /** Field description */ + @XmlJavaTypeAdapter(XmlArrayStringAdapter.class) + private String[] morePatterns = {}; + private boolean regex = false; + + public String[] getMorePatterns() { + String[] patterns; + if (morePatterns != null) { + patterns = Arrays.copyOf(morePatterns, morePatterns.length); + } else { + patterns = new String[0]; + } + return patterns; + } + } + + diff --git a/scm-core/src/main/java/sonia/scm/plugin/WebElementExtension.java b/scm-core/src/main/java/sonia/scm/plugin/WebElementExtension.java new file mode 100644 index 0000000000..e3a83b6853 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/plugin/WebElementExtension.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import lombok.Value; + +/** + * WebElementExtension can be a servlet or filter which is ready to bind. + * Those extensions are loaded by the {@link ExtensionProcessor} from a {@link WebElementDescriptor}. + * + * We don't know if we can load the defined class from the descriptor, because the class could be optional + * (annotated with {@link Requires}). So we have to load the class as string with {@link WebElementDescriptor} and when + * we know that it is safe to load the class (all requirements are fulfilled), we will create our WebElementExtension. + * + * @since 2.0.0 + */ +@Value +public class WebElementExtension { + Class clazz; + WebElementDescriptor descriptor; +} diff --git a/scm-core/src/test/java/sonia/scm/plugin/WebElementDescriptorTest.java b/scm-core/src/test/java/sonia/scm/plugin/WebElementDescriptorTest.java new file mode 100644 index 0000000000..bbbefb1dcd --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/plugin/WebElementDescriptorTest.java @@ -0,0 +1,77 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import org.junit.jupiter.api.Test; + +import javax.xml.bind.JAXB; + +import java.io.StringReader; + +import static org.assertj.core.api.Assertions.assertThat; + +class WebElementDescriptorTest { + + private static final String XML_1 = String.join("\n", + "", + " com.hitchhiker.SpaceShip", + " /space/*", + " false", + " /ship/*,/star/*", + " scm-magrathea-plugin", + " scm-earth-plugin", + "" + ); + private static final String XML_2 = String.join("\n", + "", + " com.hitchhiker.SpaceShip", + " /space/.*", + " true", + "" + ); + + @Test + void shouldUnmarshall() { + WebElementDescriptor descriptor = unmarshal(XML_1); + assertThat(descriptor.getClazz()).isEqualTo("com.hitchhiker.SpaceShip"); + assertThat(descriptor.getPattern()).isEqualTo("/space/*"); + assertThat(descriptor.getMorePatterns()).containsExactly("/ship/*", "/star/*"); + assertThat(descriptor.isRegex()).isFalse(); + assertThat(descriptor.getRequires()).containsExactlyInAnyOrder("scm-magrathea-plugin", "scm-earth-plugin"); + } + + @Test + void shouldUnmarshallWithoutMorePatterns() { + WebElementDescriptor descriptor = unmarshal(XML_2); + assertThat(descriptor.getClazz()).isEqualTo("com.hitchhiker.SpaceShip"); + assertThat(descriptor.getMorePatterns()).isEmpty(); + assertThat(descriptor.isRegex()).isTrue(); + } + + private WebElementDescriptor unmarshal(String content) { + return JAXB.unmarshal(new StringReader(content), WebElementDescriptor.class); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/filter/WebElementCollector.java b/scm-webapp/src/main/java/sonia/scm/filter/WebElementCollector.java index 90a45cc6c8..da073deefa 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/WebElementCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/WebElementCollector.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.filter; //~--- non-JDK imports -------------------------------------------------------- @@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.Priorities; import sonia.scm.plugin.PluginLoader; -import sonia.scm.plugin.WebElementDescriptor; +import sonia.scm.plugin.WebElementExtension; //~--- JDK imports ------------------------------------------------------------ @@ -67,25 +67,25 @@ public final class WebElementCollector * @param elements */ @SuppressWarnings("unchecked") - private WebElementCollector(Iterable elements) + private WebElementCollector(Iterable elements) { List> fl = Lists.newArrayList(); List> sl = Lists.newArrayList(); - for (WebElementDescriptor element : elements) + for (WebElementExtension element : elements) { if (Filter.class.isAssignableFrom(element.getClazz())) { fl.add( new TypedWebElementDescriptor<>( - (Class) element.getClazz(), element)); + (Class) element.getClazz(), element.getDescriptor())); } else if (Servlet.class.isAssignableFrom(element.getClazz())) { sl.add( new TypedWebElementDescriptor<>( - (Class) element.getClazz(), element)); + (Class) element.getClazz(), element.getDescriptor())); } else { diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmServletModule.java index 715dad4d69..ba83f5faea 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ScmServletModule.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.lifecycle.modules; import com.fasterxml.jackson.databind.ObjectMapper; @@ -193,7 +193,7 @@ class ScmServletModule extends ServletModule { // bind sslcontext provider bind(SSLContext.class).toProvider(SSLContextProvider.class); - + // bind ahc Multibinder transformers = Multibinder.newSetBinder(binder(), ContentTransformer.class); @@ -207,7 +207,7 @@ class ScmServletModule extends ServletModule { // bind new hook api bind(HookContextFactory.class); bind(HookEventFacade.class); - + // bind user-agent parser bind(UserAgentParser.class); @@ -215,7 +215,7 @@ class ScmServletModule extends ServletModule { if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP))) { filter(PATTERN_ALL).through(LoggingFilter.class); } - + // debug servlet serve(PATTERN_DEBUG).with(DebugServlet.class); diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java index 1b96f3b4bf..6cc7deed77 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultExtensionProcessor.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- @@ -115,7 +115,7 @@ public class DefaultExtensionProcessor implements ExtensionProcessor * @return */ @Override - public Iterable getWebElements() + public Iterable getWebElements() { return collector.getWebElements(); } 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 99a87f5814..dadb7618a3 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExtensionCollector.java @@ -21,22 +21,19 @@ * 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.collect.HashMultimap; import com.google.common.collect.Iterables; -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 ------------------------------------------------------------ - import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -45,6 +42,8 @@ import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -63,6 +62,7 @@ public final class ExtensionCollector for (ScmModule module : modules) { collectRootElements(moduleClassLoader, module); } + for (InstalledPlugin plugin : installedPlugins) { collectRootElements(plugin.getClassLoader(), plugin.getDescriptor()); } @@ -218,7 +218,7 @@ public final class ExtensionCollector * * @return */ - public Set getWebElements() + public Set getWebElements() { return webElements; } @@ -272,6 +272,17 @@ public final class ExtensionCollector return classes; } + private Set collectWebElementExtensions(ClassLoader defaultClassLoader, Iterable descriptors) { + Set webElementExtensions = new HashSet<>(); + for (WebElementDescriptor descriptor : descriptors) { + if (isRequirementFulfilled(descriptor)) { + Class loadedClass = loadExtension(defaultClassLoader, descriptor); + webElementExtensions.add(new WebElementExtension(loadedClass, descriptor)); + } + } + return webElementExtensions; + } + private Class loadExtension(ClassLoader classLoader, ClassElement extension) { try { return classLoader.loadClass(extension.getClazz()); @@ -307,13 +318,14 @@ public final class ExtensionCollector restProviders.addAll(collectClasses(classLoader, module.getRestProviders())); restResources.addAll(collectClasses(classLoader, module.getRestResources())); - Iterables.addAll(webElements, module.getWebElements()); + + webElements.addAll(collectWebElementExtensions(classLoader, module.getWebElements())); } //~--- fields --------------------------------------------------------------- /** Field description */ - private final Set webElements = Sets.newHashSet(); + private final Set webElements = Sets.newHashSet(); /** Field description */ private final Set restResources = Sets.newHashSet();