fixing some event payload + lifecycle + few bugs in embedded http session handling
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/845fff2d Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/845fff2d Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/845fff2d Branch: refs/heads/master Commit: 845fff2dad4dc6e7a89951ccac22bc5e14a4448e Parents: 10406df Author: Romain Manni-Bucau <[email protected]> Authored: Thu Mar 5 22:23:43 2015 +0100 Committer: Romain Manni-Bucau <[email protected]> Committed: Thu Mar 5 22:23:43 2015 +0100 ---------------------------------------------------------------------- .../openejb/cdi/CdiAppContextsService.java | 56 +++++++++++++++----- .../java/org/apache/openejb/cdi/CdiPlugin.java | 5 -- .../apache/openejb/cdi/OpenEJBLifecycle.java | 5 +- .../server/httpd/EndWebBeansListener.java | 8 +-- .../openejb/server/httpd/HttpRequestImpl.java | 41 ++++++++++---- .../openejb/server/httpd/HttpSessionImpl.java | 14 ++--- tck/cdi-embedded/pom.xml | 1 + .../tck/cdi/embedded/StandaloneTckCleaner.java | 34 ++++++++++++ tck/cdi-embedded/src/test/resources/failing.xml | 8 ++- .../catalina/cdi/ServletContextHandler.java | 22 +++++++- 10 files changed, 150 insertions(+), 44 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiAppContextsService.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiAppContextsService.java b/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiAppContextsService.java index 7e616cc..d9067db 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiAppContextsService.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiAppContextsService.java @@ -33,8 +33,10 @@ import org.apache.webbeans.context.SingletonContext; import org.apache.webbeans.conversation.ConversationImpl; import org.apache.webbeans.conversation.ConversationManager; import org.apache.webbeans.el.ELContextStore; +import org.apache.webbeans.event.EventMetadataImpl; import org.apache.webbeans.spi.ContextsService; import org.apache.webbeans.spi.ConversationService; +import org.apache.webbeans.util.AnnotationUtil; import org.apache.webbeans.web.context.ServletRequestContext; import org.apache.webbeans.web.intercept.RequestScopedBeanInterceptorHandler; @@ -56,6 +58,8 @@ import javax.enterprise.context.SessionScoped; import javax.enterprise.context.spi.Context; import javax.enterprise.util.AnnotationLiteral; import javax.inject.Singleton; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; import javax.servlet.ServletRequestEvent; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -85,6 +89,8 @@ public class CdiAppContextsService extends AbstractContextsService implements Co private final ConversationService conversationService; + private volatile Object appEvent; + private static final ThreadLocal<Collection<Runnable>> endRequestRunnables = new ThreadLocal<Collection<Runnable>>() { @Override protected Collection<Runnable> initialValue() { @@ -159,7 +165,7 @@ public class CdiAppContextsService extends AbstractContextsService implements Co return null; } - @Override + @Override // this method is called after the deployment (BeansDeployer) but need beans to be here to get events public void init(final Object initializeObject) { //Start application context startContext(ApplicationScoped.class, initializeObject); @@ -171,7 +177,10 @@ public class CdiAppContextsService extends AbstractContextsService implements Co public void beforeStop(final Object destroyObject) { { // trigger @PreDestroy mainly but keep it active until destroy(xxx) applicationContext.destroy(); - webBeansContext.getBeanManagerImpl().fireEvent(destroyObject != null ? destroyObject : applicationContext, DestroyedLiteral.APP); + webBeansContext.getBeanManagerImpl().fireEvent( + appEvent, + new EventMetadataImpl(null, ServletContext.class.isInstance(appEvent) ? ServletContext.class : Object.class,null, new Annotation[] { DestroyedLiteral.APP }, webBeansContext), + false); applicationContext.setActive(true); singletonContext.destroy(); @@ -305,10 +314,23 @@ public class CdiAppContextsService extends AbstractContextsService implements Co } private void initApplicationContext(final Object init) { // in case contexts are stop/start - final boolean alreadyStarted = applicationContext.isActive(); - if (!alreadyStarted) { - applicationContext.setActive(true); - webBeansContext.getBeanManagerImpl().fireEvent(init != null ? init : applicationContext, InitializedLiteral.APP); + if (appEvent == null) { // no need of sync cause of the lifecycle + Object event = init; + if (StartupObject.class.isInstance(init)) { + final StartupObject so = StartupObject.class.cast(init); + if (so.isFromWebApp()) { // ear webapps + event = so.getWebContext().getServletContext(); + } else if (so.getAppInfo().webAppAlone) { + event = SystemInstance.get().getComponent(ServletContext.class); + } + } else if (ServletContextEvent.class.isInstance(init)) { + event = ServletContextEvent.class.cast(init).getServletContext(); + } + appEvent = event != null ? event : applicationContext; + webBeansContext.getBeanManagerImpl().fireEvent( + appEvent, + new EventMetadataImpl(null, ServletContext.class.isInstance(appEvent) ? ServletContext.class : Object.class, null, new Annotation[] { InitializedLiteral.APP }, webBeansContext), + false); } } @@ -517,13 +539,16 @@ public class CdiAppContextsService extends AbstractContextsService implements Co final SessionContext context = sessionContext.get(); if (context != null && context.isActive()) { - context.destroy(); - pushRequestReleasable(new Runnable() { // call it at the end of the request - @Override - public void run() { - webBeansContext.getBeanManagerImpl().fireEvent(session, DestroyedLiteral.SESSION); - } - }); + if (getRequestContext(false) == null) { + doDestroySession(context, session); + } else { + pushRequestReleasable(new Runnable() { // call it at the end of the request + @Override + public void run() { + doDestroySession(context, session); + } + }); + } } //Clear thread locals @@ -535,6 +560,11 @@ public class CdiAppContextsService extends AbstractContextsService implements Co } } + private void doDestroySession(SessionContext context, HttpSession session) { + context.destroy(); + webBeansContext.getBeanManagerImpl().fireEvent(session, DestroyedLiteral.SESSION); + } + //we don't have initApplicationContext private void destroyApplicationContext() { http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiPlugin.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiPlugin.java b/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiPlugin.java index ab94535..a06ea9b 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiPlugin.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/cdi/CdiPlugin.java @@ -160,11 +160,6 @@ public class CdiPlugin extends AbstractOwbPlugin implements OpenWebBeansJavaEEPl return contexsServices; } - public void startup() { - this.contexsServices = (CdiAppContextsService) webBeansContext.getContextsService(); - this.contexsServices.init(null); - } - public void stop() throws OpenEJBException { final ClassLoader oldCl = Thread.currentThread().getContextClassLoader(); try { http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/container/openejb-core/src/main/java/org/apache/openejb/cdi/OpenEJBLifecycle.java ---------------------------------------------------------------------- diff --git a/container/openejb-core/src/main/java/org/apache/openejb/cdi/OpenEJBLifecycle.java b/container/openejb-core/src/main/java/org/apache/openejb/cdi/OpenEJBLifecycle.java index b3d7767..5747c50 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/cdi/OpenEJBLifecycle.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/cdi/OpenEJBLifecycle.java @@ -160,7 +160,6 @@ public class OpenEJBLifecycle implements ContainerLifecycle { cdiPlugin.setClassLoader(stuff.getClassLoader()); cdiPlugin.setWebBeansContext(webBeansContext); - cdiPlugin.startup(); //Configure EJB Deployments cdiPlugin.configureDeployments(stuff.getBeanContexts()); @@ -173,9 +172,6 @@ public class OpenEJBLifecycle implements ContainerLifecycle { //Deploy the beans CdiScanner cdiScanner = null; try { - //Initialize contexts - this.contextsService.init(startupObject); - //Scanning process logger.debug("Scanning classpaths for beans artifacts."); @@ -200,6 +196,7 @@ public class OpenEJBLifecycle implements ContainerLifecycle { //Deploy bean from XML. Also configures deployments, interceptors, decorators. deployer.deploy(scannerService); + contextsService.init(startupObject); // fire app event, this doesnt init anything else } catch (final Exception e1) { SystemInstance.get().getComponent(Assembler.class).logger.error("CDI Beans module deployment failed", e1); throw new OpenEJBRuntimeException(e1); http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/EndWebBeansListener.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/EndWebBeansListener.java b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/EndWebBeansListener.java index af340ac..dda5c9f 100644 --- a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/EndWebBeansListener.java +++ b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/EndWebBeansListener.java @@ -165,24 +165,26 @@ public class EndWebBeansListener implements ServletContextListener, ServletReque // ensure session ThreadLocal is set webBeansContext.getContextsService().startContext(SessionScoped.class, event.getSession()); - - webBeansContext.getContextsService().endContext(SessionScoped.class, event.getSession()); + if (WebappWebBeansContext.class.isInstance(webBeansContext)) { // end after child WebappWebBeansContext.class.cast(webBeansContext).getParent().getContextsService().endContext(SessionScoped.class, event.getSession()); } final CdiAppContextsService appContextsService = CdiAppContextsService.class.cast(webBeansContext.getContextsService()); if (appContextsService.getRequestContext(false) != null) { + final String id = event.getSession().getId(); // capture it eagerly! appContextsService.pushRequestReleasable(new Runnable() { @Override public void run() { - doDestroyConversations(event.getSession().getId()); + doDestroyConversations(id); } }); } else { doDestroyConversations(event.getSession().getId()); } + webBeansContext.getContextsService().endContext(SessionScoped.class, event.getSession()); + destroyFakedRequest(); } http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpRequestImpl.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpRequestImpl.java b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpRequestImpl.java index 9d4c0c4..2ac3b9b 100644 --- a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpRequestImpl.java +++ b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpRequestImpl.java @@ -23,6 +23,7 @@ import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.SecurityService; import org.apache.openejb.util.ArrayEnumeration; import org.apache.openejb.util.DaemonThreadFactory; +import org.apache.openejb.util.Duration; import org.apache.openejb.util.Logger; import javax.security.auth.login.LoginException; @@ -86,7 +87,7 @@ public class HttpRequestImpl implements HttpRequest { protected static final String JSESSIONID = "JSESSIONID"; // note: no eviction so invalidate has to be called properly - private static final ConcurrentMap<String, HttpSession> SESSIONS = new ConcurrentHashMap<>(); + private static final ConcurrentMap<String, RequestSession> SESSIONS = new ConcurrentHashMap<>(); public static final Class<?>[] SERVLET_CONTEXT_INTERFACES = new Class<?>[]{ServletContext.class}; public static final InvocationHandler SERVLET_CONTEXT_HANDLER = new InvocationHandler() { @@ -110,18 +111,25 @@ public class HttpRequestImpl implements HttpRequest { if (!"true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.http.eviction", "true"))) { return; } + final Duration duration = new Duration(SystemInstance.get().getProperty("openejb.http.eviction.duration", "1 minute")); es = Executors.newScheduledThreadPool(1, new DaemonThreadFactory(HttpRequestImpl.class)); es.scheduleWithFixedDelay(new Runnable() { @Override public void run() { - for (final HttpSession session : new ArrayList<>(SESSIONS.values())) { - if (session.getMaxInactiveInterval() > 0 && session.getLastAccessedTime() + session.getMaxInactiveInterval() < System.currentTimeMillis()) { + for (final RequestSession data : new ArrayList<>(SESSIONS.values())) { + final HttpSession session = data.session; + if (session.getMaxInactiveInterval() > 0 + && session.getLastAccessedTime() + TimeUnit.SECONDS.toMillis(session.getMaxInactiveInterval()) < System.currentTimeMillis()) { SESSIONS.remove(session.getId()); session.invalidate(); + + if (data.request != null && data.request.end != null) { + data.request.end.sessionDestroyed(new HttpSessionEvent(session)); + } } } } - }, 1, 1, TimeUnit.MINUTES); + }, duration.getTime(), duration.getTime(), duration.getUnit()); } private EndWebBeansListener end; @@ -430,9 +438,11 @@ public class HttpRequestImpl implements HttpRequest { for (String c : cookies) { final String current = c.trim(); if (current.startsWith("EJBSESSIONID=")) { - session = SESSIONS.get(current.substring("EJBSESSIONID=".length())); + final RequestSession requestSession = SESSIONS.get(current.substring("EJBSESSIONID=".length())); + session = requestSession == null ? null : requestSession.session; } else if (current.startsWith("JSESSIONID=")) { - session = SESSIONS.get(current.substring("JSESSIONID=".length())); + final RequestSession requestSession = SESSIONS.get(current.substring("JSESSIONID=".length())); + session = requestSession == null ? null : requestSession.session; } } } @@ -925,9 +935,9 @@ public class HttpRequestImpl implements HttpRequest { session = new SessionInvalidateListener(session, end); } - final HttpSession previous = SESSIONS.putIfAbsent(session.getId(), session); + final RequestSession previous = SESSIONS.putIfAbsent(session.getId(), new RequestSession(this, session)); if (previous != null) { - session = previous; + session = previous.session; } else { if (es == null) { synchronized (HttpRequestImpl.class) { @@ -1253,9 +1263,9 @@ public class HttpRequestImpl implements HttpRequest { public void invalidate() { SESSIONS.remove(session.getId()); try { - listener.sessionDestroyed(new HttpSessionEvent(session)); - } finally { super.invalidate(); + } finally { + listener.sessionDestroyed(new HttpSessionEvent(session)); } } } @@ -1298,4 +1308,15 @@ public class HttpRequestImpl implements HttpRequest { // not yet supported: TODO: fake response write in ByteArrayOutputStream and then call HttpListenerRegistry and write it back } } + + private static class RequestSession extends HttpSessionEvent { + private final HttpRequestImpl request; + private final HttpSession session; + + public RequestSession(final HttpRequestImpl request, final HttpSession session) { + super(session); + this.request = request; + this.session = session; + } + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpSessionImpl.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpSessionImpl.java b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpSessionImpl.java index a5c2595..6f06708 100644 --- a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpSessionImpl.java +++ b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpSessionImpl.java @@ -37,12 +37,12 @@ public class HttpSessionImpl implements HttpSession { private Collection<HttpSessionListener> listeners; private String sessionId = UUID.randomUUID().toString(); private Map<String, Object> attributes = new HashMap<String, Object>(); - private final ConcurrentMap<String, HttpSession> mapToClean; + private final ConcurrentMap<String, ? extends HttpSessionEvent> mapToClean; private final long created = System.currentTimeMillis(); - private final long timeout; + private volatile long timeout; private volatile long lastAccessed = created; - public HttpSessionImpl(final ConcurrentMap<String, HttpSession> sessions, final String contextPath, final long timeout) { + public HttpSessionImpl(final ConcurrentMap<String, ? extends HttpSessionEvent> sessions, final String contextPath, final long timeout) { this.mapToClean = sessions; this.timeout = timeout; if (contextPath == null) { @@ -170,12 +170,13 @@ public class HttpSessionImpl implements HttpSession { @Override public void setMaxInactiveInterval(int i) { - // no-op + touch(); + timeout = (long) i; } @Override public int getMaxInactiveInterval() { - touch(); + // touch(); // TODO: dont use it internally return (int) timeout; } @@ -185,7 +186,8 @@ public class HttpSessionImpl implements HttpSession { return new HttpSessionContext() { @Override public javax.servlet.http.HttpSession getSession(final String sessionId) { - return mapToClean.get(sessionId); + final HttpSessionEvent event = mapToClean.get(sessionId); + return event == null ? null : event.getSession(); } @Override http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/tck/cdi-embedded/pom.xml ---------------------------------------------------------------------- diff --git a/tck/cdi-embedded/pom.xml b/tck/cdi-embedded/pom.xml index 4133bfb..e2c9e08 100644 --- a/tck/cdi-embedded/pom.xml +++ b/tck/cdi-embedded/pom.xml @@ -207,6 +207,7 @@ <openejb.strict.interface.declaration>true</openejb.strict.interface.declaration> <openejb.http.mock-request>true</openejb.http.mock-request> <openejb.http.default-content-type>text/plain</openejb.http.default-content-type> <!-- TODO: remove it --> + <openejb.http.eviction.duration>1 second</openejb.http.eviction.duration> <openejb.embedded.try-jsp>true</openejb.embedded.try-jsp> <openejb.deploymentId.format>{appId}/{ejbJarId}/{ejbName}</openejb.deploymentId.format> <org.apache.openejb.assembler.classic.WebAppBuilder>org.apache.openejb.web.LightweightWebAppBuilder</org.apache.openejb.assembler.classic.WebAppBuilder> http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/tck/cdi-embedded/src/test/java/org/apache/openejb/tck/cdi/embedded/StandaloneTckCleaner.java ---------------------------------------------------------------------- diff --git a/tck/cdi-embedded/src/test/java/org/apache/openejb/tck/cdi/embedded/StandaloneTckCleaner.java b/tck/cdi-embedded/src/test/java/org/apache/openejb/tck/cdi/embedded/StandaloneTckCleaner.java new file mode 100644 index 0000000..a9b42a8 --- /dev/null +++ b/tck/cdi-embedded/src/test/java/org/apache/openejb/tck/cdi/embedded/StandaloneTckCleaner.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openejb.tck.cdi.embedded; + +import org.jboss.cdi.tck.util.ActionSequence; +import org.testng.IInvokedMethod; +import org.testng.IInvokedMethodListener; +import org.testng.ITestResult; + +public class StandaloneTckCleaner implements IInvokedMethodListener { + @Override + public void beforeInvocation(final IInvokedMethod iInvokedMethod, final ITestResult iTestResult) { + // no-op + } + + @Override + public void afterInvocation(final IInvokedMethod iInvokedMethod, final ITestResult iTestResult) { + ActionSequence.reset(); + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/tck/cdi-embedded/src/test/resources/failing.xml ---------------------------------------------------------------------- diff --git a/tck/cdi-embedded/src/test/resources/failing.xml b/tck/cdi-embedded/src/test/resources/failing.xml index a215086..4d56571 100644 --- a/tck/cdi-embedded/src/test/resources/failing.xml +++ b/tck/cdi-embedded/src/test/resources/failing.xml @@ -30,8 +30,12 @@ -Dopenejb.http.mock-request=true -Dopenejb.embedded.try-jsp=true --> - <classes><!-- org.jboss.cdi.tck.tests.extensions.producer.remote.RemoteProducerTest --> - <class name="org.jboss.cdi.tck.tests.extensions.processBean.ProcessBeanTest" /> + <classes> + <!-- + org.jboss.cdi.tck.interceptors.tests.contract.aroundTimeout.bindings.AroundTimeoutOrderInterceptorTest + org.jboss.cdi.tck.tests.extensions.lifecycle.processInjectionTarget.ContainerEventTest + --> + <class name="org.jboss.cdi.tck.tests.context.session.listener.SessionContextHttpSessionListenerTest" /> </classes> </test> </suite> http://git-wip-us.apache.org/repos/asf/tomee/blob/845fff2d/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/cdi/ServletContextHandler.java ---------------------------------------------------------------------- diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/cdi/ServletContextHandler.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/cdi/ServletContextHandler.java index 8bfd627..b774aee 100644 --- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/cdi/ServletContextHandler.java +++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/cdi/ServletContextHandler.java @@ -16,15 +16,35 @@ */ package org.apache.tomee.catalina.cdi; +import org.apache.catalina.connector.Request; +import org.apache.openejb.AppContext; +import org.apache.openejb.core.WebContext; +import org.apache.openejb.loader.SystemInstance; +import org.apache.openejb.spi.ContainerSystem; import org.apache.tomee.catalina.OpenEJBSecurityListener; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ServletContextHandler implements InvocationHandler { @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { // ITE are handler by Proxys - return method.invoke(OpenEJBSecurityListener.requests.get().getServletContext(), args); + final Request request = OpenEJBSecurityListener.requests.get(); + if (request != null) { + return method.invoke(request.getServletContext(), args); + } + + OpenEJBSecurityListener.requests.remove(); // can be a not container thread so clean it up + for (final AppContext a : SystemInstance.get().getComponent(ContainerSystem.class).getAppContexts()) { + for (final WebContext w : a.getWebContexts()) { + if (w.getClassLoader() == Thread.currentThread().getContextClassLoader()) { // not in CXF so == should be fine + return method.invoke(w.getServletContext(), args); + } + } + } + + throw new IllegalStateException("Didnt find a web context for " + Thread.currentThread().getContextClassLoader()); } }
