support requires annotation on WebElements

This commit is contained in:
Sebastian Sdorra
2020-04-16 11:57:38 +02:00
parent b9acc7c9f6
commit 81e8dc428c
9 changed files with 188 additions and 150 deletions

View File

@@ -26,6 +26,7 @@ package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
@@ -43,12 +44,12 @@ import java.util.Set;
* @since 2.0.0 * @since 2.0.0
*/ */
@Getter @Getter
@NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public final class ClassElement { @NoArgsConstructor(access = AccessLevel.PACKAGE)
public class ClassElement {
@XmlElement(name = "class") @XmlElement(name = "class")
private String clazz; private String clazz;
private String description; private String description;

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.plugin; package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
@@ -76,5 +76,5 @@ public interface ExtensionProcessor
* *
* @return collected web elements * @return collected web elements
*/ */
public Iterable<WebElementDescriptor> getWebElements(); public Iterable<WebElementExtension> getWebElements();
} }

View File

@@ -21,155 +21,60 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.plugin; package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- 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; 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.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 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. * {@link sonia.scm.filter.WebElement} annotation.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0
*/ */
@Getter
@AllArgsConstructor
@ToString
@EqualsAndHashCode
@XmlRootElement(name = "web-element") @XmlRootElement(name = "web-element")
@XmlAccessorType(XmlAccessType.FIELD) @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") @XmlElement(name = "value")
private String pattern; private String pattern;
/** Field description */ @XmlJavaTypeAdapter(XmlArrayStringAdapter.class)
private String[] morePatterns = {};
private boolean regex = false; 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;
}
} }

View File

@@ -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;
}

View File

@@ -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",
"<web-element>",
" <class>com.hitchhiker.SpaceShip</class>",
" <value>/space/*</value>",
" <regex>false</regex>",
" <morePatterns>/ship/*,/star/*</morePatterns>",
" <requires>scm-magrathea-plugin</requires>",
" <requires>scm-earth-plugin</requires>",
"</web-element>"
);
private static final String XML_2 = String.join("\n",
"<web-element>",
" <class>com.hitchhiker.SpaceShip</class>",
" <value>/space/.*</value>",
" <regex>true</regex>",
"</web-element>"
);
@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);
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.filter; package sonia.scm.filter;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
@@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.Priorities; import sonia.scm.Priorities;
import sonia.scm.plugin.PluginLoader; import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.WebElementDescriptor; import sonia.scm.plugin.WebElementExtension;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -67,25 +67,25 @@ public final class WebElementCollector
* @param elements * @param elements
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private WebElementCollector(Iterable<WebElementDescriptor> elements) private WebElementCollector(Iterable<WebElementExtension> elements)
{ {
List<TypedWebElementDescriptor<? extends Filter>> fl = Lists.newArrayList(); List<TypedWebElementDescriptor<? extends Filter>> fl = Lists.newArrayList();
List<TypedWebElementDescriptor<? extends HttpServlet>> sl = List<TypedWebElementDescriptor<? extends HttpServlet>> sl =
Lists.newArrayList(); Lists.newArrayList();
for (WebElementDescriptor element : elements) for (WebElementExtension element : elements)
{ {
if (Filter.class.isAssignableFrom(element.getClazz())) if (Filter.class.isAssignableFrom(element.getClazz()))
{ {
fl.add( fl.add(
new TypedWebElementDescriptor<>( new TypedWebElementDescriptor<>(
(Class<? extends Filter>) element.getClazz(), element)); (Class<? extends Filter>) element.getClazz(), element.getDescriptor()));
} }
else if (Servlet.class.isAssignableFrom(element.getClazz())) else if (Servlet.class.isAssignableFrom(element.getClazz()))
{ {
sl.add( sl.add(
new TypedWebElementDescriptor<>( new TypedWebElementDescriptor<>(
(Class<? extends HttpServlet>) element.getClazz(), element)); (Class<? extends HttpServlet>) element.getClazz(), element.getDescriptor()));
} }
else else
{ {

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.lifecycle.modules; package sonia.scm.lifecycle.modules;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -193,7 +193,7 @@ class ScmServletModule extends ServletModule {
// bind sslcontext provider // bind sslcontext provider
bind(SSLContext.class).toProvider(SSLContextProvider.class); bind(SSLContext.class).toProvider(SSLContextProvider.class);
// bind ahc // bind ahc
Multibinder<ContentTransformer> transformers = Multibinder<ContentTransformer> transformers =
Multibinder.newSetBinder(binder(), ContentTransformer.class); Multibinder.newSetBinder(binder(), ContentTransformer.class);
@@ -207,7 +207,7 @@ class ScmServletModule extends ServletModule {
// bind new hook api // bind new hook api
bind(HookContextFactory.class); bind(HookContextFactory.class);
bind(HookEventFacade.class); bind(HookEventFacade.class);
// bind user-agent parser // bind user-agent parser
bind(UserAgentParser.class); bind(UserAgentParser.class);
@@ -215,7 +215,7 @@ class ScmServletModule extends ServletModule {
if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP))) { if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP))) {
filter(PATTERN_ALL).through(LoggingFilter.class); filter(PATTERN_ALL).through(LoggingFilter.class);
} }
// debug servlet // debug servlet
serve(PATTERN_DEBUG).with(DebugServlet.class); serve(PATTERN_DEBUG).with(DebugServlet.class);

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.plugin; package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
@@ -115,7 +115,7 @@ public class DefaultExtensionProcessor implements ExtensionProcessor
* @return * @return
*/ */
@Override @Override
public Iterable<WebElementDescriptor> getWebElements() public Iterable<WebElementExtension> getWebElements()
{ {
return collector.getWebElements(); return collector.getWebElements();
} }

View File

@@ -21,22 +21,19 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.plugin; package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@@ -45,6 +42,8 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
//~--- JDK imports ------------------------------------------------------------
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
@@ -63,6 +62,7 @@ public final class ExtensionCollector
for (ScmModule module : modules) { for (ScmModule module : modules) {
collectRootElements(moduleClassLoader, module); collectRootElements(moduleClassLoader, module);
} }
for (InstalledPlugin plugin : installedPlugins) { for (InstalledPlugin plugin : installedPlugins) {
collectRootElements(plugin.getClassLoader(), plugin.getDescriptor()); collectRootElements(plugin.getClassLoader(), plugin.getDescriptor());
} }
@@ -218,7 +218,7 @@ public final class ExtensionCollector
* *
* @return * @return
*/ */
public Set<WebElementDescriptor> getWebElements() public Set<WebElementExtension> getWebElements()
{ {
return webElements; return webElements;
} }
@@ -272,6 +272,17 @@ public final class ExtensionCollector
return classes; return classes;
} }
private Set<WebElementExtension> collectWebElementExtensions(ClassLoader defaultClassLoader, Iterable<WebElementDescriptor> descriptors) {
Set<WebElementExtension> 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) { private Class<?> loadExtension(ClassLoader classLoader, ClassElement extension) {
try { try {
return classLoader.loadClass(extension.getClazz()); return classLoader.loadClass(extension.getClazz());
@@ -307,13 +318,14 @@ public final class ExtensionCollector
restProviders.addAll(collectClasses(classLoader, module.getRestProviders())); restProviders.addAll(collectClasses(classLoader, module.getRestProviders()));
restResources.addAll(collectClasses(classLoader, module.getRestResources())); restResources.addAll(collectClasses(classLoader, module.getRestResources()));
Iterables.addAll(webElements, module.getWebElements());
webElements.addAll(collectWebElementExtensions(classLoader, module.getWebElements()));
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private final Set<WebElementDescriptor> webElements = Sets.newHashSet(); private final Set<WebElementExtension> webElements = Sets.newHashSet();
/** Field description */ /** Field description */
private final Set<Class> restResources = Sets.newHashSet(); private final Set<Class> restResources = Sets.newHashSet();