diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java index 2841fd09fec..e7cb6316a9d 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.servlet; import java.io.IOException; +import java.util.function.BiFunction; import javax.servlet.ServletContext; import javax.servlet.UnavailableException; @@ -239,6 +240,47 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl } } + /** + * Wrap component using component specific Wrapper Function beans. + * + * @param component the component to optionally wrap + * @param wrapperFunctionType the bean class type to look for in the {@link ServletContextHandler} + * @param function the BiFunction to execute for each {@code wrapperFunctionType} Bean found (passing in the component and component type) + * @param the "wrapper function" implementation. (eg: {@code ServletHolder.WrapperFunction} or {@code FilterHolder.WrapperFunction}, etc) + * @return the component that has passed through all Wrapper Function beans found. + */ + protected T wrap(final T component, final Class wrapperFunctionType, final BiFunction function) + { + T ret = component; + ServletContextHandler contextHandler = getServletHandler().getServletContextHandler(); + if (contextHandler == null) + { + ContextHandler.Context context = ContextHandler.getCurrentContext(); + contextHandler = (ServletContextHandler)(context == null ? null : context.getContextHandler()); + } + + if (contextHandler != null) + { + for (W wrapperFunction : contextHandler.getBeans(wrapperFunctionType)) + { + ret = function.apply(wrapperFunction, ret); + } + } + return ret; + } + + protected T unwrap(final T component) + { + T ret = component; + + while (ret instanceof Wrapped) + { + // noinspection unchecked,rawtypes + ret = (T)((Wrapped)ret).getWrapped(); + } + return ret; + } + @Override public void dump(Appendable out, String indent) throws IOException { @@ -250,4 +292,9 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl { return Dumpable.dump(this); } + + interface Wrapped + { + C getWrapped(); + } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java index a406635393b..64b9b04335c 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java @@ -24,12 +24,16 @@ import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.List; +import java.util.Objects; import javax.servlet.DispatcherType; import javax.servlet.Filter; +import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.component.Dumpable; @@ -126,6 +130,7 @@ public class FilterHolder extends Holder throw ex; } } + _filter = wrap(_filter, FilterHolder.WrapFunction.class, FilterHolder.WrapFunction::wrapFilter); _config = new Config(); if (LOG.isDebugEnabled()) LOG.debug("Filter.init {}", _filter); @@ -170,13 +175,19 @@ public class FilterHolder extends Holder @Override public void destroyInstance(Object o) - throws Exception { if (o == null) return; - Filter f = (Filter)o; - f.destroy(); - getServletHandler().destroyFilter(f); + + Filter filter = (Filter)o; + + // need to use the unwrapped filter because lifecycle callbacks such as + // postconstruct and predestroy are based off the classname and the wrapper + // classes are unknown outside the ServletHolder + getServletHandler().destroyFilter(unwrap(filter)); + + // destroy the wrapped filter, in case there is special behaviour + filter.destroy(); } public void setFilter(Filter filter) @@ -189,6 +200,13 @@ public class FilterHolder extends Holder return _filter; } + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) + throws IOException, ServletException + { + _filter.doFilter(request, response, chain); + } + @Override public void dump(Appendable out, String indent) throws IOException { @@ -278,11 +296,69 @@ public class FilterHolder extends Holder class Config extends HolderConfig implements FilterConfig { - @Override public String getFilterName() { return getName(); } } + + /** + * Experimental Wrapper mechanism for Filter objects. + *

+ * Beans in {@code ServletContextHandler} or {@code WebAppContext} that implement this interface + * will be called to optionally wrap any newly created Filters + * (before their {@link Filter#init(FilterConfig)} method is called) + *

+ */ + public interface WrapFunction + { + /** + * Optionally wrap the Filter. + * + * @param filter the Filter being passed in. + * @return the Filter (extend from {@link FilterHolder.Wrapper} if you do wrap the Filter) + */ + Filter wrapFilter(Filter filter); + } + + public static class Wrapper implements Filter, Wrapped + { + private final Filter _filter; + + public Wrapper(Filter filter) + { + _filter = Objects.requireNonNull(filter, "Filter cannot be null"); + } + + @Override + public Filter getWrapped() + { + return _filter; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + _filter.init(filterConfig); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + _filter.doFilter(request, response, chain); + } + + @Override + public void destroy() + { + _filter.destroy(); + } + + @Override + public String toString() + { + return String.format("%s:%s", this.getClass().getSimpleName(), _filter.toString()); + } + } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java index 971a4604433..a8549817e40 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ListenerHolder.java @@ -101,6 +101,7 @@ public class ListenerHolder extends BaseHolder throw ex; } } + _listener = wrap(_listener, WrapFunction.class, WrapFunction::wrapEventListener); contextHandler.addEventListener(_listener); } } @@ -132,7 +133,7 @@ public class ListenerHolder extends BaseHolder ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler(); if (contextHandler != null) contextHandler.removeEventListener(_listener); - getServletHandler().destroyListener(_listener); + getServletHandler().destroyListener(unwrap(_listener)); } finally { @@ -146,4 +147,46 @@ public class ListenerHolder extends BaseHolder { return super.toString() + ": " + getClassName(); } + + /** + * Experimental Wrapper mechanism for Servlet EventListeners. + *

+ * Beans in {@code ServletContextHandler} or {@code WebAppContext} that implement this interface + * will be called to optionally wrap any newly created Servlet EventListeners before + * they are used for the first time. + *

+ */ + public interface WrapFunction + { + /** + * Optionally wrap the Servlet EventListener. + * + * @param listener the Servlet EventListener being passed in. + * @return the Servlet EventListener (extend from {@link ListenerHolder.Wrapper} + * if you do wrap the Servlet EventListener) + */ + EventListener wrapEventListener(EventListener listener); + } + + public static class Wrapper implements EventListener, Wrapped + { + final EventListener _listener; + + public Wrapper(EventListener listener) + { + _listener = listener; + } + + @Override + public EventListener getWrapped() + { + return _listener; + } + + @Override + public String toString() + { + return String.format("%s:%s", this.getClass().getSimpleName(), _listener.toString()); + } + } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index 2d78e68e74b..df58211b890 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -377,6 +377,11 @@ public class ServletHandler extends ScopedHandler return _servletContext; } + public ServletContextHandler getServletContextHandler() + { + return _contextHandler; + } + @ManagedAttribute(value = "mappings of servlets", readonly = true) public ServletMapping[] getServletMappings() { @@ -1575,7 +1580,6 @@ public class ServletHandler extends ScopedHandler { if (LOG.isDebugEnabled()) LOG.debug("call filter {}", _filterHolder); - Filter filter = _filterHolder.getFilter(); //if the request already does not support async, then the setting for the filter //is irrelevant. However if the request supports async but this filter does not @@ -1585,7 +1589,7 @@ public class ServletHandler extends ScopedHandler try { baseRequest.setAsyncSupported(false, _filterHolder.toString()); - filter.doFilter(request, response, _next); + _filterHolder.doFilter(request, response, _next); } finally { @@ -1593,7 +1597,7 @@ public class ServletHandler extends ScopedHandler } } else - filter.doFilter(request, response, _next); + _filterHolder.doFilter(request, response, _next); return; } @@ -1648,7 +1652,6 @@ public class ServletHandler extends ScopedHandler FilterHolder holder = _chain.get(_filter++); if (LOG.isDebugEnabled()) LOG.debug("call filter {}", holder); - Filter filter = holder.getFilter(); //if the request already does not support async, then the setting for the filter //is irrelevant. However if the request supports async but this filter does not @@ -1658,7 +1661,7 @@ public class ServletHandler extends ScopedHandler try { _baseRequest.setAsyncSupported(false, holder.toString()); - filter.doFilter(request, response, this); + holder.doFilter(request, response, this); } finally { @@ -1666,7 +1669,7 @@ public class ServletHandler extends ScopedHandler } } else - filter.doFilter(request, response, this); + holder.doFilter(request, response, this); return; } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index e3f02f6c4a4..49430e94f04 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.Stack; import java.util.concurrent.TimeUnit; @@ -445,19 +446,18 @@ public class ServletHolder extends Holder implements UserIdentity.Scope @Override public void destroyInstance(Object o) - throws Exception { if (o == null) return; - Servlet servlet = ((Servlet)o); - //need to use the unwrapped servlet because lifecycle callbacks such as - //postconstruct and predestroy are based off the classname and the wrapper - //classes are unknown outside the ServletHolder - Servlet unwrapped = servlet; - while (WrapperServlet.class.isAssignableFrom(unwrapped.getClass())) - unwrapped = ((WrapperServlet)unwrapped).getWrappedServlet(); - getServletHandler().destroyServlet(unwrapped); - //destroy the wrapped servlet, in case there is special behaviour + + Servlet servlet = (Servlet)o; + + // need to use the unwrapped servlet because lifecycle callbacks such as + // postconstruct and predestroy are based off the classname and the wrapper + // classes are unknown outside the ServletHolder + getServletHandler().destroyServlet(unwrap(servlet)); + + // destroy the wrapped servlet, in case there is special behaviour servlet.destroy(); } @@ -582,14 +582,13 @@ public class ServletHolder extends Holder implements UserIdentity.Scope _identityService = getServletHandler().getIdentityService(); if (_identityService != null) { - _runAsToken = _identityService.newRunAsToken(_runAsRole); - _servlet = new RunAsServlet(_servlet, _identityService, _runAsToken); + _servlet = new RunAs(_servlet, _identityService, _runAsToken); } } if (!isAsyncSupported()) - _servlet = new NotAsyncServlet(_servlet); + _servlet = new NotAsync(_servlet); // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet if (isJspServlet()) @@ -600,6 +599,8 @@ public class ServletHolder extends Holder implements UserIdentity.Scope else if (_forcedPath != null) detectJspContainer(); + _servlet = wrap(_servlet, WrapFunction.class, WrapFunction::wrapServlet); + if (LOG.isDebugEnabled()) LOG.debug("Servlet.init {} for {}", _servlet, getName()); _servlet.init(_config); @@ -1235,13 +1236,38 @@ public class ServletHolder extends Holder implements UserIdentity.Scope } } - private static class WrapperServlet implements Servlet + /** + * Experimental Wrapper mechanism for Servlet objects. + *

+ * Beans in {@code ServletContextHandler} or {@code WebAppContext} that implement this interface + * will be called to optionally wrap any newly created Servlets + * (before their {@link Servlet#init(ServletConfig)} method is called) + *

+ */ + public interface WrapFunction { - final Servlet _servlet; + /** + * Optionally wrap the Servlet. + * + * @param servlet the servlet being passed in. + * @return the servlet (extend from {@link ServletHolder.Wrapper} if you do wrap the Servlet) + */ + Servlet wrapServlet(Servlet servlet); + } - public WrapperServlet(Servlet servlet) + public static class Wrapper implements Servlet, Wrapped + { + private final Servlet _servlet; + + public Wrapper(Servlet servlet) { - _servlet = servlet; + _servlet = Objects.requireNonNull(servlet, "Servlet cannot be null"); + } + + @Override + public Servlet getWrapped() + { + return _servlet; } @Override @@ -1273,14 +1299,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope { _servlet.destroy(); } - - /** - * @return the original servlet - */ - public Servlet getWrappedServlet() - { - return _servlet; - } @Override public String toString() @@ -1289,12 +1307,12 @@ public class ServletHolder extends Holder implements UserIdentity.Scope } } - private static class RunAsServlet extends WrapperServlet + private static class RunAs extends Wrapper { final IdentityService _identityService; final RunAsToken _runAsToken; - public RunAsServlet(Servlet servlet, IdentityService identityService, RunAsToken runAsToken) + public RunAs(Servlet servlet, IdentityService identityService, RunAsToken runAsToken) { super(servlet); _identityService = identityService; @@ -1307,7 +1325,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); try { - _servlet.init(config); + getWrapped().init(config); } finally { @@ -1321,7 +1339,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); try { - _servlet.service(req, res); + getWrapped().service(req, res); } finally { @@ -1335,7 +1353,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); try { - _servlet.destroy(); + getWrapped().destroy(); } finally { @@ -1344,9 +1362,9 @@ public class ServletHolder extends Holder implements UserIdentity.Scope } } - private static class NotAsyncServlet extends WrapperServlet + private static class NotAsync extends Wrapper { - public NotAsyncServlet(Servlet servlet) + public NotAsync(Servlet servlet) { super(servlet); } @@ -1360,7 +1378,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope try { baseRequest.setAsyncSupported(false, this.toString()); - _servlet.service(req, res); + getWrapped().service(req, res); } finally { @@ -1369,7 +1387,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope } else { - _servlet.service(req, res); + getWrapped().service(req, res); } } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ComponentWrapTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ComponentWrapTest.java new file mode 100644 index 00000000000..3e4023319fb --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ComponentWrapTest.java @@ -0,0 +1,292 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.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.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.EventListener; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class ComponentWrapTest +{ + private static final Logger LOG = LoggerFactory.getLogger(ComponentWrapTest.class); + private Server server; + private HttpClient client; + + @BeforeEach + public void setUp() throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + client = new HttpClient(); + client.start(); + } + + @AfterEach + public void tearDown() + { + LifeCycle.stop(client); + LifeCycle.stop(server); + } + + @Test + public void testSimpleFilterServletAndListener() throws Exception + { + EventQueue events = new EventQueue(); + WrapHandler wrapHandler = new WrapHandler(events); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + ServletHolder servletHolder = new ServletHolder(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("utf-8"); + resp.getWriter().println("hello"); + } + }); + contextHandler.addServlet(servletHolder, "/hello"); + FilterHolder filterHolder = new FilterHolder(new Filter() + { + @Override + public void init(FilterConfig filterConfig) + { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + chain.doFilter(request, response); + } + + @Override + public void destroy() + { + } + }); + contextHandler.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); + + ListenerHolder listenerHolder = new ListenerHolder(LoggingRequestListener.class); + contextHandler.getServletHandler().addListener(listenerHolder); + + contextHandler.addBean(wrapHandler); + server.setHandler(contextHandler); + server.start(); + + ContentResponse response = client.GET(server.getURI().resolve("/hello")); + assertThat("Response.status", response.getStatus(), is(HttpStatus.OK_200)); + + List expectedEvents = new ArrayList<>(); + expectedEvents.add("TestWrapFilter.init()"); + expectedEvents.add("TestWrapServlet.init()"); + expectedEvents.add("TestWrapListener.requestInitialized()"); + expectedEvents.add("TestWrapFilter.doFilter()"); + expectedEvents.add("TestWrapServlet.service()"); + expectedEvents.add("TestWrapListener.requestDestroyed()"); + + List actualEvents = new ArrayList<>(); + actualEvents.addAll(events); + + assertThat("Metrics Events Count", actualEvents.size(), is(expectedEvents.size())); + } + + public static class LoggingRequestListener implements ServletRequestListener + { + @Override + public void requestDestroyed(ServletRequestEvent sre) + { + LOG.info("requestDestroyed()"); + } + + @Override + public void requestInitialized(ServletRequestEvent sre) + { + LOG.info("requestInitialized()"); + } + } + + public static class EventQueue extends LinkedBlockingQueue + { + private static final Logger LOG = LoggerFactory.getLogger(EventQueue.class); + + public void addEvent(String format, Object... args) + { + String eventText = String.format(format, args); + offer(eventText); + Throwable cause = null; + if (args.length > 0) + { + Object lastArg = args[args.length - 1]; + if (lastArg instanceof Throwable) + { + cause = (Throwable)lastArg; + } + } + LOG.info("[EVENT] {}", eventText, cause); + } + } + + public static class WrapHandler implements + FilterHolder.WrapFunction, + ServletHolder.WrapFunction, + ListenerHolder.WrapFunction + { + private EventQueue events; + + public WrapHandler(EventQueue events) + { + this.events = events; + } + + @Override + public Filter wrapFilter(Filter filter) + { + return new TestWrapFilter(filter, events); + } + + @Override + public EventListener wrapEventListener(EventListener listener) + { + if (listener instanceof ServletRequestListener) + { + return new TestWrapListener((ServletRequestListener)listener, events); + } + return listener; + } + + @Override + public Servlet wrapServlet(Servlet servlet) + { + return new TestWrapServlet(servlet, events); + } + } + + public static class TestWrapFilter extends FilterHolder.Wrapper + { + private EventQueue events; + + public TestWrapFilter(Filter filter, EventQueue events) + { + super(filter); + this.events = events; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + events.addEvent("TestWrapFilter.init()"); + super.init(filterConfig); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + events.addEvent("TestWrapFilter.doFilter()"); + super.doFilter(request, response, chain); + } + } + + public static class TestWrapServlet extends ServletHolder.Wrapper + { + private EventQueue events; + + public TestWrapServlet(Servlet servlet, EventQueue events) + { + super(servlet); + this.events = events; + } + + @Override + public void init(ServletConfig config) throws ServletException + { + events.addEvent("TestWrapServlet.init()"); + super.init(config); + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + events.addEvent("TestWrapServlet.service()"); + super.service(req, res); + } + } + + public static class TestWrapListener extends ListenerHolder.Wrapper implements ServletRequestListener + { + private ServletRequestListener requestListener; + private EventQueue events; + + public TestWrapListener(ServletRequestListener listener, EventQueue events) + { + super(listener); + this.requestListener = listener; + this.events = events; + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) + { + this.events.addEvent("TestWrapListener.requestDestroyed()"); + requestListener.requestDestroyed(sre); + } + + @Override + public void requestInitialized(ServletRequestEvent sre) + { + this.events.addEvent("TestWrapListener.requestInitialized()"); + requestListener.requestInitialized(sre); + } + } +} diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java index a78c49d870d..7832100e4e6 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletLifeCycleTest.java @@ -108,10 +108,10 @@ public class ServletLifeCycleTest server.stop(); assertThat(events, Matchers.contains( - "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", - "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", + "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter2", "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", + "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestFilter", "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3", "destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet3", "Destroy class org.eclipse.jetty.servlet.ServletLifeCycleTest$TestServlet2", diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java index 7bce5454db7..25ed5982587 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/Configuration.java @@ -179,4 +179,74 @@ public interface Configuration * @return true if configuration should be aborted */ boolean abort(WebAppContext context); + + /** + * Experimental Wrapper mechanism for WebApp Configuration components. + *

+ * Beans in WebAppContext that implement this interface + * will be called to optionally wrap any newly created {@link Configuration} + * objects before they are used for the first time. + *

+ */ + interface WrapperFunction + { + Configuration wrapConfiguration(Configuration configuration); + } + + class Wrapper implements Configuration + { + private Configuration delegate; + + public Wrapper(Configuration delegate) + { + this.delegate = delegate; + } + + public Configuration getWrapped() + { + return delegate; + } + + @Override + public void preConfigure(WebAppContext context) throws Exception + { + delegate.preConfigure(context); + } + + @Override + public void configure(WebAppContext context) throws Exception + { + delegate.configure(context); + } + + @Override + public void postConfigure(WebAppContext context) throws Exception + { + delegate.postConfigure(context); + } + + @Override + public void deconfigure(WebAppContext context) throws Exception + { + delegate.deconfigure(context); + } + + @Override + public void destroy(WebAppContext context) throws Exception + { + delegate.destroy(context); + } + + @Override + public boolean isEnabledByDefault() + { + return delegate.isEnabledByDefault(); + } + + @Override + public boolean abort(WebAppContext context) + { + return delegate.abort(context); + } + } } diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index f5d83aec70f..64ef0b6311d 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java @@ -532,6 +532,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL _metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames()); Boolean validate = (Boolean)getAttribute(MetaData.VALIDATE_XML); _metadata.setValidateXml((validate != null && validate)); + wrapConfigurations(); preConfigure(); super.doStart(); postConfigure(); @@ -550,6 +551,26 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL } } + private void wrapConfigurations() + { + Collection wrappers = getBeans(Configuration.WrapperFunction.class); + if (wrappers == null || wrappers.isEmpty()) + return; + + List configs = new ArrayList<>(_configurations.getConfigurations()); + _configurations.clear(); + + for (Configuration config : configs) + { + Configuration wrapped = config; + for (Configuration.WrapperFunction wrapperFunction : getBeans(Configuration.WrapperFunction.class)) + { + wrapped = wrapperFunction.wrapConfiguration(wrapped); + } + _configurations.add(wrapped); + } + } + @Override public void destroy() { diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java index f3c6b9b03bd..e78ca39f752 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java @@ -185,15 +185,22 @@ public class WebAppContextTest Configurations.cleanKnown(); WebAppContext wac = new WebAppContext(); wac.setServer(new Server()); - assertThat(wac.getConfigurations().stream().map(c -> c.getClass().getName()).collect(Collectors.toList()), - Matchers.contains( - "org.eclipse.jetty.webapp.JmxConfiguration", - "org.eclipse.jetty.webapp.WebInfConfiguration", - "org.eclipse.jetty.webapp.WebXmlConfiguration", - "org.eclipse.jetty.webapp.MetaInfConfiguration", - "org.eclipse.jetty.webapp.FragmentConfiguration", - "org.eclipse.jetty.webapp.WebAppConfiguration", - "org.eclipse.jetty.webapp.JettyWebXmlConfiguration")); + List actualConfigurations = wac.getConfigurations().stream().map(c -> c.getClass().getName()).collect(Collectors.toList()); + List expectedConfigurations = new ArrayList<>(); + + JmxConfiguration jmx = new JmxConfiguration(); + if (jmx.isAvailable()) // depending on JVM runtime, this might not be available when this test is run + { + expectedConfigurations.add("org.eclipse.jetty.webapp.JmxConfiguration"); + } + expectedConfigurations.add("org.eclipse.jetty.webapp.WebInfConfiguration"); + expectedConfigurations.add("org.eclipse.jetty.webapp.WebXmlConfiguration"); + expectedConfigurations.add("org.eclipse.jetty.webapp.MetaInfConfiguration"); + expectedConfigurations.add("org.eclipse.jetty.webapp.FragmentConfiguration"); + expectedConfigurations.add("org.eclipse.jetty.webapp.WebAppConfiguration"); + expectedConfigurations.add("org.eclipse.jetty.webapp.JettyWebXmlConfiguration"); + + assertThat(actualConfigurations, Matchers.contains(expectedConfigurations.toArray())); } @Test