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 884e4f5d322d..046d9126b8c7 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; @@ -185,6 +186,47 @@ public synchronized boolean isInstance() return _instance != null; } + /** + * 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 { @@ -196,4 +238,9 @@ public String dump() { 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 38682af0da95..ded28c8769f9 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.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; @@ -128,6 +132,7 @@ public void initialize() throws Exception throw ex; } } + _filter = wrap(_filter, FilterHolder.WrapFunction.class, FilterHolder.WrapFunction::wrapFilter); _config = new Config(); if (LOG.isDebugEnabled()) LOG.debug("Filter.init {}", _filter); @@ -156,13 +161,19 @@ public void doStop() @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 synchronized void setFilter(Filter filter) @@ -175,6 +186,13 @@ public Filter getFilter() 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 { @@ -264,11 +282,69 @@ public Collection getUrlPatternMappings() 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 16a888d9c132..ef10588e5d3c 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 @@ -102,6 +102,7 @@ public void doStart() throws Exception throw ex; } } + _listener = wrap(_listener, WrapFunction.class, WrapFunction::wrapEventListener); contextHandler.addEventListener(_listener); } } @@ -117,7 +118,7 @@ public void doStop() throws Exception ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler(); if (contextHandler != null) contextHandler.removeEventListener(_listener); - getServletHandler().destroyListener(_listener); + getServletHandler().destroyListener(unwrap(_listener)); } finally { @@ -131,4 +132,46 @@ public String toString() { 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 00e34d91fed1..1979d285e6ab 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 @@ -395,6 +395,11 @@ public ServletContext getServletContext() return _servletContext; } + public ServletContextHandler getServletContextHandler() + { + return _contextHandler; + } + @ManagedAttribute(value = "mappings of servlets", readonly = true) public ServletMapping[] getServletMappings() { @@ -1617,7 +1622,6 @@ public void doFilter(ServletRequest request, ServletResponse response) { 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 @@ -1627,7 +1631,7 @@ public void doFilter(ServletRequest request, ServletResponse response) try { baseRequest.setAsyncSupported(false, _filterHolder.toString()); - filter.doFilter(request, response, _next); + _filterHolder.doFilter(request, response, _next); } finally { @@ -1635,7 +1639,7 @@ public void doFilter(ServletRequest request, ServletResponse response) } } else - filter.doFilter(request, response, _next); + _filterHolder.doFilter(request, response, _next); return; } @@ -1690,7 +1694,6 @@ public void doFilter(ServletRequest request, ServletResponse response) 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 @@ -1700,7 +1703,7 @@ public void doFilter(ServletRequest request, ServletResponse response) try { _baseRequest.setAsyncSupported(false, holder.toString()); - filter.doFilter(request, response, this); + holder.doFilter(request, response, this); } finally { @@ -1708,7 +1711,7 @@ public void doFilter(ServletRequest request, ServletResponse response) } } 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 8a39f77b83e4..4929c16f9300 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.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; @@ -440,19 +441,18 @@ public void doStop() @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(); } @@ -565,7 +565,7 @@ private synchronized void initServlet() _servlet = newInstance(); if (_config == null) _config = new Config(); - + //check run-as rolename and convert to token from IdentityService if (_runAsRole == null) { @@ -577,14 +577,13 @@ private synchronized void initServlet() _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()) @@ -596,6 +595,7 @@ else if (_forcedPath != null) detectJspContainer(); initMultiPart(); + _servlet = wrap(_servlet, WrapFunction.class, WrapFunction::wrapServlet); if (LOG.isDebugEnabled()) LOG.debug("Servlet.init {} for {}", _servlet, getName()); @@ -1166,7 +1166,6 @@ protected Servlet newInstance() throws ServletException, IllegalAccessException, if (ctx != null) return ctx.createServlet(getHeldClass()); return getHeldClass().getDeclaredConstructor().newInstance(); - } catch (ServletException ex) { @@ -1247,7 +1246,7 @@ else if (System.nanoTime() < _available) synchronized (ServletHolder.this) { ServletHolder.this._servlet = this._servlet; - _servlet.service(req,res); + _servlet.service(req, res); } } } @@ -1274,13 +1273,38 @@ public UnavailableException getUnavailableException() } } - 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 static class Wrapper implements Servlet, Wrapped + { + private final Servlet _servlet; - public WrapperServlet(Servlet servlet) + public Wrapper(Servlet servlet) + { + _servlet = Objects.requireNonNull(servlet, "Servlet cannot be null"); + } + + @Override + public Servlet getWrapped() { - _servlet = servlet; + return _servlet; } @Override @@ -1312,14 +1336,6 @@ public void destroy() { _servlet.destroy(); } - - /** - * @return the original servlet - */ - public Servlet getWrappedServlet() - { - return _servlet; - } @Override public String toString() @@ -1328,12 +1344,12 @@ public String toString() } } - 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; @@ -1346,7 +1362,7 @@ public void init(ServletConfig config) throws ServletException Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); try { - _servlet.init(config); + getWrapped().init(config); } finally { @@ -1360,7 +1376,7 @@ public void service(ServletRequest req, ServletResponse res) throws ServletExcep Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); try { - _servlet.service(req, res); + getWrapped().service(req, res); } finally { @@ -1374,7 +1390,7 @@ public void destroy() Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken); try { - _servlet.destroy(); + getWrapped().destroy(); } finally { @@ -1383,9 +1399,9 @@ public void destroy() } } - private static class NotAsyncServlet extends WrapperServlet + private static class NotAsync extends Wrapper { - public NotAsyncServlet(Servlet servlet) + public NotAsync(Servlet servlet) { super(servlet); } @@ -1399,7 +1415,7 @@ public void service(ServletRequest req, ServletResponse res) throws ServletExcep try { baseRequest.setAsyncSupported(false, this.toString()); - _servlet.service(req, res); + getWrapped().service(req, res); } finally { @@ -1408,7 +1424,7 @@ public void service(ServletRequest req, ServletResponse res) throws ServletExcep } 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 000000000000..f5c38a3e645f --- /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. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +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.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class ComponentWrapTest +{ + private static final Logger LOG = Log.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 = Log.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 4587572027fc..0e49712f836b 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 @@ -110,10 +110,10 @@ public void testLifeCycle() throws Exception 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 aba1a28b8e9c..48ffd12e63f0 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 @@ -96,6 +96,70 @@ public interface Configuration */ void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception; + /** + * 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 void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception + { + delegate.cloneConfigure(template, context); + } + } + class ClassList extends ArrayList { 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 14df9557bd68..616958e636e5 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 @@ -980,10 +980,23 @@ protected void loadConfigurations() } for (String configClass : _configurationClasses) { - _configurations.add((Configuration)Loader.loadClass(configClass).getDeclaredConstructor().newInstance()); + @SuppressWarnings("unchecked") + Configuration configuration = (Configuration)Loader.loadClass(configClass).getDeclaredConstructor().newInstance(); + configuration = wrap(configuration); + _configurations.add(configuration); } } + private Configuration wrap(final Configuration configuration) + { + Configuration ret = configuration; + for (Configuration.WrapperFunction wrapperFunction : getBeans(Configuration.WrapperFunction.class)) + { + ret = wrapperFunction.wrapConfiguration(ret); + } + return ret; + } + @Override public String toString() {