Merge pull request #5273 from eclipse/jetty-9.4.x-5032-minimal-extensible-web-apps

Issue #5032 - Minimal Extensible Web App changes
This commit is contained in:
Joakim Erdfelt 2020-09-17 06:00:00 -05:00 committed by GitHub
commit 0db3663a11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 606 additions and 52 deletions

View File

@ -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 abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpabl
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 <W> 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 <W> T wrap(final T component, final Class<W> wrapperFunctionType, final BiFunction<W, T, T> 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 abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpabl
{
return Dumpable.dump(this);
}
interface Wrapped<C>
{
C getWrapped();
}
}

View File

@ -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;
@ -128,6 +132,7 @@ public class FilterHolder extends Holder<Filter>
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 class FilterHolder extends Holder<Filter>
@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 class FilterHolder extends Holder<Filter>
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 class FilterHolder extends Holder<Filter>
class Config extends HolderConfig implements FilterConfig
{
@Override
public String getFilterName()
{
return getName();
}
}
/**
* Experimental Wrapper mechanism for Filter objects.
* <p>
* 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)
* </p>
*/
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<Filter>
{
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());
}
}
}

View File

@ -102,6 +102,7 @@ public class ListenerHolder extends BaseHolder<EventListener>
throw ex;
}
}
_listener = wrap(_listener, WrapFunction.class, WrapFunction::wrapEventListener);
contextHandler.addEventListener(_listener);
}
}
@ -117,7 +118,7 @@ public class ListenerHolder extends BaseHolder<EventListener>
ContextHandler contextHandler = ContextHandler.getCurrentContext().getContextHandler();
if (contextHandler != null)
contextHandler.removeEventListener(_listener);
getServletHandler().destroyListener(_listener);
getServletHandler().destroyListener(unwrap(_listener));
}
finally
{
@ -131,4 +132,46 @@ public class ListenerHolder extends BaseHolder<EventListener>
{
return super.toString() + ": " + getClassName();
}
/**
* Experimental Wrapper mechanism for Servlet EventListeners.
* <p>
* 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.
* </p>
*/
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<EventListener>
{
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());
}
}
}

View File

@ -395,6 +395,11 @@ public class ServletHandler extends ScopedHandler
return _servletContext;
}
public ServletContextHandler getServletContextHandler()
{
return _contextHandler;
}
@ManagedAttribute(value = "mappings of servlets", readonly = true)
public ServletMapping[] getServletMappings()
{
@ -1617,7 +1622,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
@ -1627,7 +1631,7 @@ public class ServletHandler extends ScopedHandler
try
{
baseRequest.setAsyncSupported(false, _filterHolder.toString());
filter.doFilter(request, response, _next);
_filterHolder.doFilter(request, response, _next);
}
finally
{
@ -1635,7 +1639,7 @@ public class ServletHandler extends ScopedHandler
}
}
else
filter.doFilter(request, response, _next);
_filterHolder.doFilter(request, response, _next);
return;
}
@ -1690,7 +1694,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
@ -1700,7 +1703,7 @@ public class ServletHandler extends ScopedHandler
try
{
_baseRequest.setAsyncSupported(false, holder.toString());
filter.doFilter(request, response, this);
holder.doFilter(request, response, this);
}
finally
{
@ -1708,7 +1711,7 @@ public class ServletHandler extends ScopedHandler
}
}
else
filter.doFilter(request, response, this);
holder.doFilter(request, response, this);
return;
}

View File

@ -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;
@ -440,19 +441,18 @@ public class ServletHolder extends Holder<Servlet> 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();
}
@ -565,7 +565,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
_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 @@ public class ServletHolder extends Holder<Servlet> 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())
@ -596,6 +595,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
detectJspContainer();
initMultiPart();
_servlet = wrap(_servlet, WrapFunction.class, WrapFunction::wrapServlet);
if (LOG.isDebugEnabled())
LOG.debug("Servlet.init {} for {}", _servlet, getName());
@ -1166,7 +1166,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
if (ctx != null)
return ctx.createServlet(getHeldClass());
return getHeldClass().getDeclaredConstructor().newInstance();
}
catch (ServletException ex)
{
@ -1247,7 +1246,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
synchronized (ServletHolder.this)
{
ServletHolder.this._servlet = this._servlet;
_servlet.service(req,res);
_servlet.service(req, res);
}
}
}
@ -1274,13 +1273,38 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
}
}
private static class WrapperServlet implements Servlet
/**
* Experimental Wrapper mechanism for Servlet objects.
* <p>
* 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)
* </p>
*/
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<Servlet>
{
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
@ -1312,14 +1336,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
{
_servlet.destroy();
}
/**
* @return the original servlet
*/
public Servlet getWrappedServlet()
{
return _servlet;
}
@Override
public String toString()
@ -1328,12 +1344,12 @@ public class ServletHolder extends Holder<Servlet> 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;
@ -1346,7 +1362,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
try
{
_servlet.init(config);
getWrapped().init(config);
}
finally
{
@ -1360,7 +1376,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
try
{
_servlet.service(req, res);
getWrapped().service(req, res);
}
finally
{
@ -1374,7 +1390,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
Object oldRunAs = _identityService.setRunAs(_identityService.getSystemUserIdentity(), _runAsToken);
try
{
_servlet.destroy();
getWrapped().destroy();
}
finally
{
@ -1383,9 +1399,9 @@ public class ServletHolder extends Holder<Servlet> 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);
}
@ -1399,7 +1415,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
try
{
baseRequest.setAsyncSupported(false, this.toString());
_servlet.service(req, res);
getWrapped().service(req, res);
}
finally
{
@ -1408,7 +1424,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
}
else
{
_servlet.service(req, res);
getWrapped().service(req, res);
}
}
}

View File

@ -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<String> 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<String> 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<String>
{
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);
}
}
}

View File

@ -110,10 +110,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",

View File

@ -96,6 +96,70 @@ public interface Configuration
*/
void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception;
/**
* Experimental Wrapper mechanism for WebApp Configuration components.
* <p>
* 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.
* </p>
*/
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<String>
{

View File

@ -980,10 +980,23 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
}
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()
{