diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java index c2ac20317568..8f398a8be25c 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java @@ -18,6 +18,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -39,9 +40,12 @@ import javax.servlet.annotation.HandlesTypes; import org.eclipse.jetty.annotations.AnnotationParser.Handler; -import org.eclipse.jetty.plus.annotation.ContainerInitializer; import org.eclipse.jetty.plus.webapp.PlusConfiguration; +import org.eclipse.jetty.servlet.ServletContainerInitializerHolder; +import org.eclipse.jetty.servlet.Source; +import org.eclipse.jetty.servlet.Source.Origin; import org.eclipse.jetty.util.JavaVersion; +import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.StringUtil; @@ -53,6 +57,7 @@ import org.eclipse.jetty.webapp.FragmentDescriptor; import org.eclipse.jetty.webapp.JettyWebXmlConfiguration; import org.eclipse.jetty.webapp.MetaInfConfiguration; +import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebDescriptor; import org.eclipse.jetty.webapp.WebXmlConfiguration; @@ -80,6 +85,7 @@ public class AnnotationConfiguration extends AbstractConfiguration protected final List _discoverableAnnotationHandlers = new ArrayList<>(); protected ClassInheritanceHandler _classInheritanceHandler; protected final List _containerInitializerAnnotationHandlers = new ArrayList<>(); + protected final List _sciHolders = new ArrayList<>(); protected List _parserTasks; @@ -87,7 +93,6 @@ public class AnnotationConfiguration extends AbstractConfiguration protected CounterStatistic _webInfLibStats; protected CounterStatistic _webInfClassesStats; protected Pattern _sciExcludePattern; - protected List _initializers; public AnnotationConfiguration() { @@ -314,6 +319,116 @@ public int compare(ServletContainerInitializer sci1, ServletContainerInitializer return Integer.compare(i1, i2); } } + + public static class DiscoveredServletContainerInitializerHolder extends ServletContainerInitializerHolder + { + private Set> _handlesTypes = new HashSet<>(); + private Set _discoveredClassNames = new HashSet<>(); + + public DiscoveredServletContainerInitializerHolder(Source source, ServletContainerInitializer sci, Class... startupClasses) + { + super(source, sci); + //take the classes and set them aside until we can calculate all of their + //subclasses as necessary + _handlesTypes.addAll(_startupClasses); + } + + /** + * Classes that have annotations that are listed in @HandlesTypes + * are discovered by the ContainerInitializerAnnotationHandler + * and added here. + * @param names of classnames that have an annotation that is listed as a class in HandlesTypes + */ + @Override + public void addStartupClasses(String... names) + { + _discoveredClassNames.addAll(Arrays.asList(names)); + } + + /** + * Classes that are listed in @HandlesTypes and found by + * the createServletContainerInitializerAnnotationHandlers method. + * @param clazzes classes listed in HandlesTypes + */ + @Override + public void addStartupClasses(Class... clazzes) + { + _handlesTypes.addAll(Arrays.asList(clazzes)); + } + + @Override + protected Set> resolveStartupClasses() throws Exception + { + final Set> classes = new HashSet<>(); + WebAppClassLoader.runWithServerClassAccess(() -> + { + for (String name:_startupClassNames) + { + classes.add(Loader.loadClass(name)); + } + return null; + }); + return classes; + } + + /** + * Process each of the classes that are not annotations from @HandlesTypes and + * find all of the subclasses/implementations. + * Also process all of the classes that were discovered to have an annotation + * that was listed in @HandlesTypes, and find all of their subclasses/implementations + * in order to generate a complete set of classnames that can be passed into the + * onStartup method. + * + * @param classMap complete inheritance tree of all classes in the webapp, can be + * null if @HandlesTypes did not specify any classes. + */ + void resolveClasses(Map> classMap) + { + Set finalClassnames = new HashSet<>(); + + if (classMap != null) + { + for (Class c : _handlesTypes) + { + //find all subclasses/implementations of the classes (not annotations) named in @HandlesTypes + if (!c.isAnnotation()) + addInheritedTypes(finalClassnames, classMap, (Set)classMap.get(c.getName())); + } + + for (String classname:_discoveredClassNames) + { + //add each of the classes that were discovered to have an annotation listed in @HandlesTypes + finalClassnames.add(classname); + //walk its hierarchy and find all types that extend or implement the class + addInheritedTypes(finalClassnames, classMap, (Set)classMap.get(classname)); + } + } + + //finally, add the complete set of startup classnames + super.addStartupClasses(finalClassnames.toArray(new String[0])); + } + + /** + * Recursively walk the class hierarchy for the given set of classnames. + * + * @param results all classes related to the set of classnames in names + * @param classMap full inheritance tree for all classes in the webapp + * @param names the names of classes for which to walk the hierarchy + */ + private void addInheritedTypes(Set results, Map> classMap, Set names) + { + if (names == null || names.isEmpty()) + return; + + for (String s : names) + { + results.add(s); + + //walk the hierarchy and find all types that extend or implement the class + addInheritedTypes(results, classMap, (Set)classMap.get(s)); + } + } + } @Override public void preConfigure(final WebAppContext context) throws Exception @@ -351,17 +466,12 @@ public void configure(WebAppContext context) throws Exception if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty()) scanForAnnotations(context); - - // Resolve container initializers - List initializers = - (List)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS); - if (initializers != null && initializers.size() > 0) + + Map> map = (Map>)context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP); + for (DiscoveredServletContainerInitializerHolder holder:_sciHolders) { - Map> map = (Map>)context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP); - for (ContainerInitializer i : initializers) - { - i.resolveClasses(context, map); - } + holder.resolveClasses(map); + context.addServletContainerInitializer(holder); //only add the holder now all classes are fully available } } @@ -373,17 +483,10 @@ public void postConfigure(WebAppContext context) throws Exception classMap.clear(); context.removeAttribute(CLASS_INHERITANCE_MAP); - List initializers = (List)context.getAttribute(CONTAINER_INITIALIZERS); - if (initializers != null) - initializers.clear(); - context.removeAttribute(CONTAINER_INITIALIZERS); - - if (_discoverableAnnotationHandlers != null) - _discoverableAnnotationHandlers.clear(); - + _discoverableAnnotationHandlers.clear(); _classInheritanceHandler = null; - if (_containerInitializerAnnotationHandlers != null) - _containerInitializerAnnotationHandlers.clear(); + _containerInitializerAnnotationHandlers.clear(); + _sciHolders.clear(); if (_parserTasks != null) { @@ -391,16 +494,6 @@ public void postConfigure(WebAppContext context) throws Exception _parserTasks = null; } - ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER); - if (starter != null) - { - context.removeBean(starter); - context.removeAttribute(CONTAINER_INITIALIZER_STARTER); - } - - if (_initializers != null) - _initializers.clear(); - super.postConfigure(context); } @@ -568,74 +661,46 @@ public void createServletContainerInitializerAnnotationHandlers(WebAppContext co { if (scis == null || scis.isEmpty()) return; // nothing to do - - List initializers = new ArrayList(); - context.setAttribute(CONTAINER_INITIALIZERS, initializers); - - for (ServletContainerInitializer service : scis) + + for (ServletContainerInitializer sci : scis) { - HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class); - ContainerInitializer initializer = null; + Class[] classes = new Class[0]; + HandlesTypes annotation = sci.getClass().getAnnotation(HandlesTypes.class); if (annotation != null) - { - //There is a HandlesTypes annotation on the on the ServletContainerInitializer - Class[] classes = annotation.value(); - if (classes != null) + classes = annotation.value(); + + DiscoveredServletContainerInitializerHolder holder = new DiscoveredServletContainerInitializerHolder(new Source(Origin.ANNOTATION, sci.getClass().getName()), sci); + _sciHolders.add(holder); + + if (classes.length > 0) + { + if (LOG.isDebugEnabled()) + LOG.debug("HandlesTypes {} on initializer {}", Arrays.asList(classes), sci.getClass()); + + //If we haven't already done so, we need to register a handler that will + //process the whole class hierarchy to satisfy the ServletContainerInitializer + if (context.getAttribute(CLASS_INHERITANCE_MAP) == null) { - - if (LOG.isDebugEnabled()) - { - LOG.debug("HandlesTypes {} on initializer {}", Arrays.asList(classes), service.getClass()); - } - - initializer = new ContainerInitializer(service, classes); - - //If we haven't already done so, we need to register a handler that will - //process the whole class hierarchy to satisfy the ServletContainerInitializer - if (context.getAttribute(CLASS_INHERITANCE_MAP) == null) + Map> map = new ClassInheritanceMap(); + context.setAttribute(CLASS_INHERITANCE_MAP, map); + _classInheritanceHandler = new ClassInheritanceHandler(map); + } + + for (Class c : classes) + { + //The value of one of the HandlesTypes classes is actually an Annotation itself so + //register a handler for it to discover all classes that contain this annotation + if (c.isAnnotation()) { - //MultiMap map = new MultiMap<>(); - Map> map = new ClassInheritanceMap(); - context.setAttribute(CLASS_INHERITANCE_MAP, map); - _classInheritanceHandler = new ClassInheritanceHandler(map); + if (LOG.isDebugEnabled()) + LOG.debug("Registering annotation handler for {}", c.getName()); + _containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(holder, c)); } - for (Class c : classes) - { - //The value of one of the HandlesTypes classes is actually an Annotation itself so - //register a handler for it - if (c.isAnnotation()) - { - if (LOG.isDebugEnabled()) - LOG.debug("Registering annotation handler for {}", c.getName()); - _containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c)); - } - } + holder.addStartupClasses(c); } - else - { - initializer = new ContainerInitializer(service, null); - if (LOG.isDebugEnabled()) - LOG.debug("No classes in HandlesTypes on initializer {}", service.getClass()); - } - } - else - { - initializer = new ContainerInitializer(service, null); - if (LOG.isDebugEnabled()) - LOG.debug("No HandlesTypes annotation on initializer {}", service.getClass()); } - - initializers.add(initializer); } - - //add a bean to the context which will call the servletcontainerinitializers when appropriate - ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER); - if (starter != null) - throw new IllegalStateException("ServletContainerInitializersStarter already exists"); - starter = new ServletContainerInitializersStarter(context); - context.setAttribute(CONTAINER_INITIALIZER_STARTER, starter); - context.addBean(starter, true); } public Resource getJarFor(ServletContainerInitializer service) diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java index 4b1161240074..1baa7ae6c47e 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerAnnotationHandler.java @@ -13,11 +13,14 @@ package org.eclipse.jetty.annotations; +import java.util.Objects; + import org.eclipse.jetty.annotations.AnnotationParser.AbstractHandler; import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo; import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo; import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo; import org.eclipse.jetty.plus.annotation.ContainerInitializer; +import org.eclipse.jetty.servlet.ServletContainerInitializerHolder; /** * ContainerInitializerAnnotationHandler @@ -29,12 +32,22 @@ public class ContainerInitializerAnnotationHandler extends AbstractHandler { final ContainerInitializer _initializer; - final Class _annotation; + final ServletContainerInitializerHolder _holder; + final Class _annotation; - public ContainerInitializerAnnotationHandler(ContainerInitializer initializer, Class annotation) + @Deprecated + public ContainerInitializerAnnotationHandler(ContainerInitializer initializer, Class annotation) { + _holder = null; + _annotation = Objects.requireNonNull(annotation); _initializer = initializer; - _annotation = annotation; + } + + public ContainerInitializerAnnotationHandler(ServletContainerInitializerHolder holder, Class annotation) + { + _holder = Objects.requireNonNull(holder); + _annotation = Objects.requireNonNull(annotation); + _initializer = null; } /** @@ -45,10 +58,13 @@ public ContainerInitializerAnnotationHandler(ContainerInitializer initializer, C @Override public void handle(ClassInfo info, String annotationName) { - if (annotationName == null || !_annotation.getName().equals(annotationName)) + if (!_annotation.getName().equals(annotationName)) return; - _initializer.addAnnotatedTypeName(info.getClassName()); + if (_initializer != null) + _initializer.addAnnotatedTypeName(info.getClassName()); + else + _holder.addStartupClasses(info.getClassName()); } /** @@ -59,9 +75,13 @@ public void handle(ClassInfo info, String annotationName) @Override public void handle(FieldInfo info, String annotationName) { - if (annotationName == null || !_annotation.getName().equals(annotationName)) + if (!_annotation.getName().equals(annotationName)) return; - _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName()); + + if (_initializer != null) + _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName()); + else + _holder.addStartupClasses(info.getClassInfo().getClassName()); } /** @@ -72,11 +92,15 @@ public void handle(FieldInfo info, String annotationName) @Override public void handle(MethodInfo info, String annotationName) { - if (annotationName == null || !_annotation.getName().equals(annotationName)) + if (!_annotation.getName().equals(annotationName)) return; - _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName()); + if (_initializer != null) + _initializer.addAnnotatedTypeName(info.getClassInfo().getClassName()); + else + _holder.addStartupClasses(info.getClassInfo().getClassName()); } + @Deprecated public ContainerInitializer getContainerInitializer() { return _initializer; diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java index e2be40126c0d..9696877b8e12 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializersStarter.java @@ -27,7 +27,9 @@ * * Call the onStartup() method on all ServletContainerInitializers, after having * found all applicable classes (if any) to pass in as args. + * @deprecated */ +@Deprecated public class ServletContainerInitializersStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller { private static final Logger LOG = LoggerFactory.getLogger(ServletContainerInitializersStarter.class); diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java index 2f016ed852c5..becf8c70b790 100644 --- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java @@ -16,10 +16,18 @@ import java.io.File; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Set; import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import org.eclipse.jetty.annotations.AnnotationConfiguration.ClassInheritanceMap; +import org.eclipse.jetty.annotations.AnnotationConfiguration.DiscoveredServletContainerInitializerHolder; +import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.JAR; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; @@ -30,6 +38,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -46,7 +56,7 @@ public void assertAnnotationDiscovery(boolean b) else assertFalse(_discoverableAnnotationHandlers.isEmpty()); } - } + } public File web25; @@ -194,6 +204,57 @@ public void testServerAndWebappSCIs() throws Exception Thread.currentThread().setContextClassLoader(old); } } + + @Test + public void testClassScanHandlersForSCIs() throws Exception + { + //test that SCIs with a @HandlesTypes that is an annotation registers + //handlers for the scanning phase that will capture the class hierarchy, + //and also capture all classes that contain the annotation + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(webAppLoader); + + try + { + class MyAnnotationConfiguration extends AnnotationConfiguration + { + + @Override + public void createServletContainerInitializerAnnotationHandlers(WebAppContext context, List scis) throws Exception + { + super.createServletContainerInitializerAnnotationHandlers(context, scis); + //check class hierarchy scanner handler is registered + assertNotNull(_classInheritanceHandler); + //check + assertEquals(1, _containerInitializerAnnotationHandlers.size()); + ContainerInitializerAnnotationHandler handler = _containerInitializerAnnotationHandlers.get(0); + assertThat(handler._holder.toString(), containsString("com.acme.initializer.FooInitializer")); + assertEquals("com.acme.initializer.Foo", handler._annotation.getName()); + } + } + + MyAnnotationConfiguration config = new MyAnnotationConfiguration(); + + WebAppContext context = new WebAppContext(); + List scis; + + context.setClassLoader(webAppLoader); + context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL())); + context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31true))); + context.getMetaData().setWebInfClassesResources(classes); + context.getServletContext().setEffectiveMajorVersion(3); + context.getServletContext().setEffectiveMinorVersion(1); + scis = config.getNonExcludedInitializers(context); + assertNotNull(scis); + assertEquals(3, scis.size()); + + config.createServletContainerInitializerAnnotationHandlers(context, scis); + } + finally + { + Thread.currentThread().setContextClassLoader(old); + } + } @Test public void testMetaDataCompleteSCIs() throws Exception diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestDiscoveredServletContainerInitializerHolder.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestDiscoveredServletContainerInitializerHolder.java new file mode 100644 index 000000000000..256aeaaecdc2 --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestDiscoveredServletContainerInitializerHolder.java @@ -0,0 +1,99 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.annotations; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.HandlesTypes; + +import org.eclipse.jetty.annotations.AnnotationConfiguration.DiscoveredServletContainerInitializerHolder; +import org.eclipse.jetty.servlet.Source; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +public class TestDiscoveredServletContainerInitializerHolder +{ + /** + * A marker type that is passed as an arg to @HandlesTypes + */ + interface Ordinary + { + + } + + /** + * An class with an annotation (that is listed in @HandlesTypes) + */ + @Sample(value = 1) + public static class ASample + { + } + + /** + * A class that extends a class with an annotation + */ + public static class BSample extends ASample + { + } + + @HandlesTypes({Sample.class}) + public static class SampleServletContainerInitializer implements ServletContainerInitializer + { + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + } + } + + @Test + public void test() throws Exception + { + //SCI with @HandlesTypes[Ordinary, Sample] + SampleServletContainerInitializer sci = new SampleServletContainerInitializer(); + + DiscoveredServletContainerInitializerHolder holder = + new DiscoveredServletContainerInitializerHolder(new Source(Source.Origin.ANNOTATION, sci.getClass().getName()), + sci); + + //add the @HandlesTypes to the holder + holder.addStartupClasses(Ordinary.class, Sample.class); + + //pretend scanned and discovered that ASample has the Sample annotation + holder.addStartupClasses(ASample.class.getName()); + + //pretend we scanned the entire class hierarchy and found: + // com.acme.tom and com.acme.dick both extend Ordinary + // ASample has subclass BSample + Map> classMap = new HashMap<>(); + classMap.put(Ordinary.class.getName(), new HashSet(Arrays.asList("com.acme.tom", "com.acme.dick"))); + classMap.put(ASample.class.getName(), new HashSet(Arrays.asList(BSample.class.getName()))); + holder.resolveClasses(classMap); + + //we should now have the following classes that will be passed to the SampleServletContainerInitializer.onStartup + String toString = holder.toString(); + assertThat(toString, containsString("com.acme.tom")); + assertThat(toString, containsString("com.acme.dick")); + assertThat(toString, containsString(ASample.class.getName())); + assertThat(toString, containsString(BSample.class.getName())); + assertThat(toString, containsString("applicable=[],annotated=[]")); + } +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java index bcb856b2ede7..2b242c775655 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated public class ContainerInitializer { private static final Logger LOG = LoggerFactory.getLogger(ContainerInitializer.class); diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java index 31c9aadc4ef7..b5bfc0d1ff16 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartDescriptorProcessor.java @@ -22,6 +22,7 @@ import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.annotations.ServletContainerInitializersStarter; import org.eclipse.jetty.plus.annotation.ContainerInitializer; +import org.eclipse.jetty.servlet.ServletContainerInitializerHolder; import org.eclipse.jetty.servlet.ServletMapping; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; @@ -171,9 +172,10 @@ public void visitContextParam(WebAppContext context, Descriptor descriptor, XmlP case AnnotationConfiguration.CONTAINER_INITIALIZERS: { - for (String i : values) + for (String s : values) { - visitContainerInitializer(context, new ContainerInitializer(Thread.currentThread().getContextClassLoader(), i)); + visitServletContainerInitializerHolder(context, + ServletContainerInitializerHolder.fromString(Thread.currentThread().getContextClassLoader(), s)); } break; } @@ -216,6 +218,7 @@ public void visitContextParam(WebAppContext context, Descriptor descriptor, XmlP } } + @Deprecated public void visitContainerInitializer(WebAppContext context, ContainerInitializer containerInitializer) { if (containerInitializer == null) @@ -240,6 +243,19 @@ public void visitContainerInitializer(WebAppContext context, ContainerInitialize context.addBean(starter, true); } } + + /** + * Ensure the ServletContainerInitializerHolder will be started by adding it to the context. + * + * @param context the context to which to add the ServletContainerInitializerHolder + * @param sciHolder the ServletContainerInitializerHolder + */ + public void visitServletContainerInitializerHolder(WebAppContext context, ServletContainerInitializerHolder sciHolder) + { + if (sciHolder == null) + return; + context.addServletContainerInitializer(sciHolder); + } public void visitMetaInfResource(WebAppContext context, Resource dir) { diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java index 6facfd07c777..e69bbc44dc40 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/QuickStartGeneratorConfiguration.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ListenerHolder; import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig; +import org.eclipse.jetty.servlet.ServletContextHandler.ServletContainerInitializerStarter; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; @@ -168,7 +169,11 @@ public void generateQuickStartWebXml(WebAppContext context, OutputStream stream) // The library order addContextParamFromAttribute(context, out, ServletContext.ORDERED_LIBS); //the servlet container initializers - addContextParamFromAttribute(context, out, AnnotationConfiguration.CONTAINER_INITIALIZERS); + //addContextParamFromAttribute(context, out, AnnotationConfiguration.CONTAINER_INITIALIZERS); + //TODO think of better label rather than the unused attribute, also how to retrieve the scis + ServletContainerInitializerStarter sciStarter = context.getBean(ServletContainerInitializerStarter.class); + addContextParamFromCollection(context, out, AnnotationConfiguration.CONTAINER_INITIALIZERS, + sciStarter == null ? Collections.emptySet() : sciStarter.getServletContainerInitializerHolders()); //the tlds discovered addContextParamFromAttribute(context, out, MetaInfConfiguration.METAINF_TLDS, normalizer); //the META-INF/resources discovered @@ -602,18 +607,14 @@ public void generateQuickStartWebXml(WebAppContext context, OutputStream stream) out.closeTag(); } - /** - * Turn attribute into context-param to store. - */ - private void addContextParamFromAttribute(WebAppContext context, XmlAppendable out, String attribute) throws IOException + private void addContextParamFromCollection(WebAppContext context, XmlAppendable out, String name, Collection collection) + throws IOException { - Object o = context.getAttribute(attribute); - if (o == null) + if (collection == null) return; - Collection c = (o instanceof Collection) ? (Collection)o : Collections.singletonList(o); StringBuilder v = new StringBuilder(); - for (Object i : c) + for (Object i : collection) { if (i != null) { @@ -625,10 +626,23 @@ private void addContextParamFromAttribute(WebAppContext context, XmlAppendable o } } out.openTag("context-param") - .tag("param-name", attribute) + .tag("param-name", name) .tagCDATA("param-value", v.toString()) .closeTag(); } + + /** + * Turn attribute into context-param to store. + */ + private void addContextParamFromAttribute(WebAppContext context, XmlAppendable out, String attribute) throws IOException + { + Object o = context.getAttribute(attribute); + if (o == null) + return; + + Collection c = (o instanceof Collection) ? (Collection)o : Collections.singletonList(o); + addContextParamFromCollection(context, out, attribute, c); + } /** * Turn context attribute into context-param to store. diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContainerInitializerHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContainerInitializerHolder.java new file mode 100644 index 000000000000..92e09065d5bc --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContainerInitializerHolder.java @@ -0,0 +1,258 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.servlet; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.StringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Holds a ServletContainerInitializer. + */ +public class ServletContainerInitializerHolder extends BaseHolder +{ + private static final Logger LOG = LoggerFactory.getLogger(ServletContainerInitializerHolder.class); + protected Set _startupClassNames = new HashSet<>(); + protected Set> _startupClasses = new HashSet<>(); + public static final Pattern __pattern = Pattern.compile("ContainerInitializer\\{([^,]*),interested=(\\[[^\\]]*\\])(,applicable=(\\[[^\\]]*\\]))?(,annotated=(\\[[^\\]]*\\]))?\\}"); + + protected ServletContainerInitializerHolder(Source source) + { + super(source); + } + + public ServletContainerInitializerHolder() + { + this(Source.EMBEDDED); + } + + public ServletContainerInitializerHolder(Class sciClass) + { + super(Source.EMBEDDED); + setHeldClass(sciClass); + } + + public ServletContainerInitializerHolder(Class sciClass, Class... startupClasses) + { + super(Source.EMBEDDED); + setHeldClass(sciClass); + _startupClasses.addAll(Arrays.asList(startupClasses)); + } + + public ServletContainerInitializerHolder(ServletContainerInitializer sci, Class... startupClasses) + { + this(Source.EMBEDDED, sci, startupClasses); + } + + public ServletContainerInitializerHolder(Source source, ServletContainerInitializer sci, Class... startupClasses) + { + super(source); + setInstance(sci); + if (startupClasses != null) + _startupClasses.addAll(Arrays.asList(startupClasses)); + } + + /** + * @param names the names of classes which should be passed to the SCI onStartup method + */ + public void addStartupClasses(String... names) + { + Collections.addAll(_startupClassNames, names); + } + + /** + * @param clazzes classes that should be passed to the SCI onStartup method + */ + public void addStartupClasses(Class... clazzes) + { + Collections.addAll(_startupClasses, clazzes); + } + + protected Set> resolveStartupClasses() throws Exception + { + Set> classes = new HashSet<>(); + for (String name : _startupClassNames) + { + classes.add(Loader.loadClass(name)); //TODO catch CNFE? + } + return classes; + } + + @Override + public void doStart() throws Exception + { + //Ensure all startup classes are also loaded + Set> classes = new HashSet<>(_startupClasses); + + //Ensure SCI class is loaded + super.doStart(); + + //load all classnames + classes.addAll(resolveStartupClasses()); + + ContextHandler.Context ctx = null; + if (getServletHandler() != null) + { + ctx = getServletHandler().getServletContextHandler().getServletContext(); + } + + if (ctx == null && ContextHandler.getCurrentContext() != null) + ctx = ContextHandler.getCurrentContext(); + if (ctx == null) + throw new IllegalStateException("No Context"); + + ServletContainerInitializer initializer = getInstance(); + if (initializer == null) + { + //create an instance of the SCI + initializer = createInstance(); + initializer = wrap(initializer, WrapFunction.class, WrapFunction::wrapServletContainerInitializer); + } + + try + { + + ctx.setExtendedListenerTypes(true); + if (LOG.isDebugEnabled()) + { + long start = System.nanoTime(); + initializer.onStartup(classes, ctx); + LOG.debug("ServletContainerInitializer {} called in {}ms", getClassName(), TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)); + } + else + initializer.onStartup(classes, ctx); + } + finally + { + ctx.setExtendedListenerTypes(false); + } + } + + /** + * Re-inflate a stringified ServletContainerInitializerHolder. + * + * @param loader the classloader to use to load the startup classes + * @param string the stringified representation of the ServletContainerInitializerHolder + * + * @return a new ServletContainerInitializerHolder instance populated by the info in the string + */ + public static ServletContainerInitializerHolder fromString(ClassLoader loader, String string) + { + Matcher m = __pattern.matcher(string); + + if (!m.matches()) + throw new IllegalArgumentException(string); + + try + { + //load the ServletContainerInitializer and create an instance + String sciClassname = m.group(1); + ServletContainerInitializer sci = (ServletContainerInitializer)loader.loadClass(sciClassname).getDeclaredConstructor().newInstance(); + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(new Source(Source.Origin.ANNOTATION, sciClassname)); + holder.setInstance(sci); + + //ensure all classes to be passed to onStartup are resolved + Set> classes = new HashSet<>(); + String[] classnames = StringUtil.arrayFromString(m.group(2)); + for (String name:classnames) + classes.add(loader.loadClass(name)); + + classnames = StringUtil.arrayFromString(m.group(4)); + for (String name:classnames) + classes.add(loader.loadClass(name)); + + classnames = StringUtil.arrayFromString(m.group(6)); + for (String name:classnames) + classes.add(loader.loadClass(name)); + + holder.addStartupClasses(classes.toArray(new Class[0])); + + return holder; + } + catch (Exception e) + { + throw new IllegalArgumentException(string, e); + } + } + + @Override + public String toString() + { + Set interested = new HashSet<>(_startupClassNames); + _startupClasses.forEach((c) -> interested.add(c.getName())); + //for backward compatibility the old output format must be retained + return String.format("ContainerInitializer{%s,interested=%s,applicable=%s,annotated=%s}", getClassName(), interested, Collections.emptySet(), Collections.emptySet()); + } + + /** + * Experimental Wrapper mechanism for ServletContainerInitializer objects. + *

+ * Beans in {@code ServletContextHandler} or {@code WebAppContext} that implement this interface + * will be called to optionally wrap any newly created ServletContainerInitializers + * (before their onStartup method is called) + *

+ */ + public interface WrapFunction + { + /** + * Optionally wrap the ServletContainerInitializer. + * + * @param sci the ServletContainerInitializer being passed in. + * @return the sci(extend from {@link ServletContainerInitializerHolder.Wrapper} if you do wrap the ServletContainerInitializer) + */ + ServletContainerInitializer wrapServletContainerInitializer(ServletContainerInitializer sci); + } + + public static class Wrapper implements ServletContainerInitializer, Wrapped + { + private final ServletContainerInitializer _wrappedSCI; + + public Wrapper(ServletContainerInitializer sci) + { + _wrappedSCI = Objects.requireNonNull(sci, "ServletContainerInitializer cannot be null"); + } + + @Override + public ServletContainerInitializer getWrapped() + { + return _wrappedSCI; + } + + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + _wrappedSCI.onStartup(c, ctx); + } + + @Override + public String toString() + { + return String.format("%s:%s", this.getClass().getSimpleName(), _wrappedSCI.toString()); + } + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 3f701c647f63..d420675241bd 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -68,6 +68,7 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -505,6 +506,56 @@ public FilterHolder addFilter(String filterClass, String pathSpec, EnumSet... classes) + { + if (!isStopped()) + throw new IllegalStateException("ServletContainerInitializers should be added before starting"); + + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(sci, classes); + addServletContainerInitializer(holder); + return holder; + } + + /** + * Convenience method to programmatically add a list of {@link javax.servlet.ServletContainerInitializer}. + * The initializers are guaranteed to be called in the order they are passed into this method. + * @param sciHolders the ServletContainerInitializerHolders + */ + public void addServletContainerInitializer(ServletContainerInitializerHolder... sciHolders) + { + ServletContainerInitializerStarter starter = getBean(ServletContainerInitializerStarter.class); + if (starter == null) + { + //add the starter as bean which will start when the context is started + //NOTE: do not use addManaged(starter) because this will start the + //starter immediately, which may not be before we have parsed web.xml + starter = new ServletContainerInitializerStarter(); + addBean(starter, true); + } + starter.addServletContainerInitializerHolders(sciHolders); + } + /** * notification that a ServletRegistration has been created so we can track the annotations * @@ -1545,7 +1596,9 @@ public void setResponseCharacterEncoding(String encoding) * A utility class to hold a {@link ServletContainerInitializer} and implement the * {@link ServletContainerInitializerCaller} interface so that the SCI is correctly * started if an instance of this class is added as a bean to a {@link ServletContextHandler}. + * @deprecated */ + @Deprecated public static class Initializer extends AbstractLifeCycle implements ServletContainerInitializerCaller { private final ServletContextHandler _context; @@ -1579,4 +1632,45 @@ protected void doStart() throws Exception } } } + + /** + * Bean that is added to the ServletContextHandler to start all of the + * ServletContainerInitializers by starting their corresponding + * ServletContainerInitializerHolders when this bean is itself started. + * Note that the SCIs will be started in order of addition. + */ + public static class ServletContainerInitializerStarter extends ContainerLifeCycle implements ServletContainerInitializerCaller + { + public void addServletContainerInitializerHolders(ServletContainerInitializerHolder... holders) + { + for (ServletContainerInitializerHolder holder:holders) + addBean(holder, true); + } + + public Collection getServletContainerInitializerHolders() + { + return getContainedBeans(ServletContainerInitializerHolder.class); + } + + @Override + protected void doStart() throws Exception + { + if (LOG.isDebugEnabled()) + LOG.debug("Starting SCIs"); + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + //remove all of the non-programmatic holders + Collection holders = getServletContainerInitializerHolders(); + for (ServletContainerInitializerHolder h : holders) + { + if (h.getSource().getOrigin() != Source.Origin.EMBEDDED) + removeBean(h); + } + super.doStop(); + } + } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContainerInitializerHolderTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContainerInitializerHolderTest.java new file mode 100644 index 000000000000..8acbac5d559f --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContainerInitializerHolderTest.java @@ -0,0 +1,121 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.servlet; + +import java.util.Set; +import java.util.regex.Matcher; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.eclipse.jetty.servlet.Source.Origin; +import org.eclipse.jetty.util.StringUtil; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ServletContainerInitializerHolderTest +{ + public static final String[] EMPTY_ARRAY = {}; + + static class SimpleSCI implements ServletContainerInitializer + { + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + ctx.setAttribute("SimpleSCI-onStartup", Boolean.TRUE); + } + } + + @Test + public void testClassNoArgs() throws Exception + { + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(SimpleSCI.class); + assertEquals(Source.EMBEDDED, holder.getSource()); + assertEquals("ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[],applicable=[],annotated=[]}", holder.toString()); + } + + @Test + public void testClassWithStartupClasses() throws Exception + { + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(SimpleSCI.class, Integer.class); + assertEquals(Source.EMBEDDED, holder.getSource()); + assertEquals(SimpleSCI.class, holder.getHeldClass()); + assertEquals("ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[java.lang.Integer],applicable=[],annotated=[]}", holder.toString()); + } + + @Test + public void testInstanceWithStartupClasses() throws Exception + { + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(new SimpleSCI(), Integer.class); + assertEquals(Source.EMBEDDED, holder.getSource()); + assertEquals(SimpleSCI.class, holder.getHeldClass()); + assertEquals("ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[java.lang.Integer],applicable=[],annotated=[]}", holder.toString()); + } + + @Test + public void testInstanceWithStartupClassesAndSource() throws Exception + { + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(new Source(Origin.ANNOTATION, null), new SimpleSCI(), Integer.class); + assertEquals(Origin.ANNOTATION, holder.getSource().getOrigin()); + assertEquals(SimpleSCI.class, holder.getHeldClass()); + assertEquals("ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[java.lang.Integer],applicable=[],annotated=[]}", holder.toString()); + } + + @Test + public void testToString() throws Exception + { + //test that the stringified ServletContainerInitializerHolder is backward compatible with what was generated by the old ContainerInitializer + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(SimpleSCI.class); + assertEquals("ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[],applicable=[],annotated=[]}", holder.toString()); + } + + @Test + public void testFromString() throws Exception + { + //test for backward compatibility of string format + String sci0 = "ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[],applicable=[],annotated=[]}"; + ServletContainerInitializerHolder holder = ServletContainerInitializerHolder.fromString(Thread.currentThread().getContextClassLoader(), sci0); + assertEquals(sci0, holder.toString()); + + //test with no classes + String sci1 = "ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[]}"; + String sci1Expected = "ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[],applicable=[],annotated=[]}"; + holder = ServletContainerInitializerHolder.fromString(Thread.currentThread().getContextClassLoader(), sci1); + assertEquals(sci1Expected, holder.toString()); + + //test with some startup classes + String sci2 = "ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[java.lang.String, java.lang.Integer]}"; + holder = ServletContainerInitializerHolder.fromString(Thread.currentThread().getContextClassLoader(), sci2); + + final Matcher matcher2 = ServletContainerInitializerHolder.__pattern.matcher(holder.toString()); + matcher2.matches(); + assertEquals("org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI", matcher2.group(1)); + assertThat(StringUtil.arrayFromString("[java.lang.String, java.lang.Integer]"), arrayContainingInAnyOrder(StringUtil.arrayFromString(matcher2.group(2)))); + assertThat(EMPTY_ARRAY, arrayContainingInAnyOrder(StringUtil.arrayFromString(matcher2.group(4)))); + assertThat(EMPTY_ARRAY, arrayContainingInAnyOrder(StringUtil.arrayFromString(matcher2.group(6)))); + + //test with old format with startup classes + String sci3 = "ContainerInitializer{org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI,interested=[java.lang.String, java.lang.Integer],applicable=[java.lang.Boolean],annotated=[java.lang.Long]}"; + holder = ServletContainerInitializerHolder.fromString(Thread.currentThread().getContextClassLoader(), sci3); + final Matcher matcher3 = ServletContainerInitializerHolder.__pattern.matcher(holder.toString()); + matcher3.matches(); + assertEquals("org.eclipse.jetty.servlet.ServletContainerInitializerHolderTest$SimpleSCI", matcher3.group(1)); + assertThat(StringUtil.arrayFromString("[java.lang.String, java.lang.Integer, java.lang.Boolean, java.lang.Long]"), arrayContainingInAnyOrder(StringUtil.arrayFromString(matcher3.group(2)))); + assertThat(EMPTY_ARRAY, arrayContainingInAnyOrder(StringUtil.arrayFromString(matcher3.group(4)))); + assertThat(EMPTY_ARRAY, arrayContainingInAnyOrder(StringUtil.arrayFromString(matcher3.group(6)))); + } +} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java index e07c99c30b26..2b76e6e2d7ec 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java @@ -16,9 +16,11 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.EventListener; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -74,6 +76,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.servlet.ServletContextHandler.ServletContainerInitializerStarter; import org.eclipse.jetty.util.DecoratedObjectFactory; import org.eclipse.jetty.util.Decorator; import org.eclipse.jetty.util.component.AbstractLifeCycle; @@ -1541,6 +1544,83 @@ public void testNullServletRegistration() throws Exception String response = _connector.getResponse(request.toString()); assertThat("Response", response, containsString("Test")); } + + @Test + public void testAddServletContainerInitializer() throws Exception + { + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + final AtomicBoolean called = new AtomicBoolean(); + context.addServletContainerInitializer(new ServletContainerInitializer() + { + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + called.set(true); + } + }); + + _server.setHandler(context); + _server.start(); + ServletContainerInitializerStarter starter = context.getBean(ServletContainerInitializerStarter.class); + assertNotNull(starter); + Collection holders = starter.getContainedBeans(ServletContainerInitializerHolder.class); + assertEquals(1, holders.size()); + assertTrue(called.get()); + } + + @Test + public void testAddServletContainerInitializerWithArgs() throws Exception + { + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + + final Set> onStartupClasses = new HashSet<>(); + context.addServletContainerInitializer(new ServletContainerInitializer() + { + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + onStartupClasses.addAll(c); + } + }, HelloServlet.class, TestServlet.class); + + _server.setHandler(context); + _server.start(); + + ServletContainerInitializerStarter starter = context.getBean(ServletContainerInitializerStarter.class); + assertNotNull(starter); + Collection holders = starter.getContainedBeans(ServletContainerInitializerHolder.class); + assertEquals(1, holders.size()); + assertThat(onStartupClasses, Matchers.containsInAnyOrder(HelloServlet.class, TestServlet.class)); + } + + @Test + public void testAddServletContainerInitializerHolder() throws Exception + { + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + + final Set> onStartupClasses = new HashSet<>(); + ServletContainerInitializerHolder holder = new ServletContainerInitializerHolder(Source.EMBEDDED, + new ServletContainerInitializer() + { + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + onStartupClasses.addAll(c); + } + }, HelloServlet.class, TestServlet.class); + + context.addServletContainerInitializer(holder); + _server.setHandler(context); + _server.start(); + ServletContainerInitializerStarter starter = context.getBean(ServletContainerInitializerStarter.class); + assertNotNull(starter); + Collection holders = starter.getContainedBeans(ServletContainerInitializerHolder.class); + assertEquals(1, holders.size()); + assertThat(onStartupClasses, Matchers.containsInAnyOrder(HelloServlet.class, TestServlet.class)); + } @Test public void testHandlerBeforeServletHandler() throws Exception