TOMEE-1571 destroying http session of openejb-http layer when undeploying applications with openejb embedded arquillian adapter
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/5af485d0 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/5af485d0 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/5af485d0 Branch: refs/heads/fb_tomee2_owb16 Commit: 5af485d0d271fd8c30b279290d173e88fbf37fd5 Parents: 075c465 Author: Romain Manni-Bucau <rmannibu...@apache.org> Authored: Sun May 3 22:20:13 2015 +0200 Committer: Romain Manni-Bucau <rmannibu...@apache.org> Committed: Sun May 3 22:20:13 2015 +0200 ---------------------------------------------------------------------- .../openejb/OpenEJBDeployableContainer.java | 30 ++++- .../http/WebArchiveResourceProvider.java | 16 +++ .../arquillian/openejb/SessionDestroyTest.java | 113 ++++++++++++++++ .../openejb/server/httpd/HttpRequestImpl.java | 124 +++++------------ .../openejb/server/httpd/HttpResponseImpl.java | 3 +- .../openejb/server/httpd/HttpSessionImpl.java | 26 ++-- .../server/httpd/OpenEJBHttpRegistry.java | 2 + .../openejb/server/httpd/OpenEJBHttpServer.java | 9 +- .../server/httpd/session/SessionManager.java | 132 +++++++++++++++++++ .../httpd/HttpResponseImplSessionTest.java | 16 +-- 10 files changed, 347 insertions(+), 124 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/OpenEJBDeployableContainer.java ---------------------------------------------------------------------- diff --git a/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/OpenEJBDeployableContainer.java b/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/OpenEJBDeployableContainer.java index 90d86b1..44915b8 100644 --- a/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/OpenEJBDeployableContainer.java +++ b/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/OpenEJBDeployableContainer.java @@ -37,8 +37,10 @@ import org.apache.openejb.config.DeploymentFilterable; import org.apache.openejb.config.WebModule; import org.apache.openejb.core.LocalInitialContext; import org.apache.openejb.core.LocalInitialContextFactory; +import org.apache.openejb.core.WebContext; import org.apache.openejb.loader.IO; import org.apache.openejb.loader.SystemInstance; +import org.apache.openejb.server.httpd.session.SessionManager; import org.apache.openejb.web.LightweightWebAppBuilder; import org.apache.webbeans.web.lifecycle.test.MockHttpSession; import org.apache.webbeans.web.lifecycle.test.MockServletContext; @@ -58,11 +60,6 @@ import org.jboss.arquillian.test.spi.annotation.SuiteScoped; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.descriptor.api.Descriptor; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpSession; import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; @@ -75,6 +72,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; import static org.apache.openejb.cdi.ScopeHelper.startContexts; import static org.apache.openejb.cdi.ScopeHelper.stopContexts; @@ -295,7 +297,8 @@ public class OpenEJBDeployableContainer implements DeployableContainer<OpenEJBCo final AppInfo appInfo = configurationFactory.configureApplication(module); final WebAppBuilder webAppBuilder = SystemInstance.get().getComponent(WebAppBuilder.class); - if (webAppBuilder != null && LightweightWebAppBuilder.class.isInstance(webAppBuilder)) { + final boolean isEmbeddedWebAppBuilder = webAppBuilder != null && LightweightWebAppBuilder.class.isInstance(webAppBuilder); + if (isEmbeddedWebAppBuilder) { // for now we keep the same classloader, open to discussion if we should recreate it, not sure it does worth it final LightweightWebAppBuilder lightweightWebAppBuilder = LightweightWebAppBuilder.class.cast(webAppBuilder); for (final WebModule w : module.getWebModules()) { @@ -310,6 +313,21 @@ public class OpenEJBDeployableContainer implements DeployableContainer<OpenEJBCo } } final AppContext appCtx = assembler.createApplication(appInfo, module.getClassLoader()); + if (isEmbeddedWebAppBuilder && PROPERTIES.containsKey(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE) && !appCtx.getWebContexts().isEmpty()) { + cls.add(new Closeable() { + @Override + public void close() throws IOException { + try { + final SessionManager sessionManager = SystemInstance.get().getComponent(SessionManager.class); + for (final WebContext web : appCtx.getWebContexts()) { + sessionManager.destroy(web); + } + } catch (final Throwable e) { + // no-op + } + } + }); + } final ServletContext appServletContext = new MockServletContext(); final HttpSession appSession = new MockHttpSession(); http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/http/WebArchiveResourceProvider.java ---------------------------------------------------------------------- diff --git a/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/http/WebArchiveResourceProvider.java b/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/http/WebArchiveResourceProvider.java index 2f1f731..5fd0b04 100644 --- a/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/http/WebArchiveResourceProvider.java +++ b/arquillian/arquillian-openejb-embedded-5/src/main/java/org/apache/openejb/arquillian/openejb/http/WebArchiveResourceProvider.java @@ -1,3 +1,19 @@ +/* + * 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.arquillian.openejb.http; import org.apache.openejb.arquillian.openejb.SWClassLoader; http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/arquillian/arquillian-openejb-embedded-5/src/test/java/org/apache/openejb/arquillian/openejb/SessionDestroyTest.java ---------------------------------------------------------------------- diff --git a/arquillian/arquillian-openejb-embedded-5/src/test/java/org/apache/openejb/arquillian/openejb/SessionDestroyTest.java b/arquillian/arquillian-openejb-embedded-5/src/test/java/org/apache/openejb/arquillian/openejb/SessionDestroyTest.java new file mode 100644 index 0000000..fbae01e --- /dev/null +++ b/arquillian/arquillian-openejb-embedded-5/src/test/java/org/apache/openejb/arquillian/openejb/SessionDestroyTest.java @@ -0,0 +1,113 @@ +/** + * 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.arquillian.openejb; + +import org.apache.openejb.loader.IO; +import org.jboss.arquillian.container.test.api.Deployer; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.net.URL; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebListener; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(Arquillian.class) +public class SessionDestroyTest { + @Deployment(name = "app", managed = false, testable = false) + public static Archive<?> app() { + return ShrinkWrap.create(WebArchive.class).addClasses(SessionTestManager.class, SessionListener.class); + } + + @ArquillianResource + private Deployer deployer; + + private static String id; + + @Test + @InSequence(1) + public void deploy() { + reset(); + deployer.deploy("app"); + } + + @Test + @InSequence(2) + @OperateOnDeployment("app") + public void doTest(@ArquillianResource final URL url) throws IOException { + id = IO.slurp(new URL(url.toExternalForm() + "create")); + assertNotNull(SessionListener.created); + assertEquals(id, SessionListener.created); + } + + @Test + @InSequence(3) + public void undeployAndAsserts() { + deployer.undeploy("app"); + assertNotNull(SessionListener.destroyed); + assertEquals(id, SessionListener.destroyed); + reset(); + } + + private void reset() { + SessionListener.destroyed = null; + SessionListener.created = null; + id = null; + } + + @WebServlet("/create") + public static class SessionTestManager extends HttpServlet { + @Override + protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + req.getSession().setAttribute("test", "ok"); + resp.getWriter().write(req.getSession().getId()); + } + } + + @WebListener + public static class SessionListener implements HttpSessionListener { + private static String created; + private static String destroyed; + + @Override + public void sessionCreated(final HttpSessionEvent httpSessionEvent) { + created = httpSessionEvent.getSession().getId(); + } + + @Override + public void sessionDestroyed(final HttpSessionEvent httpSessionEvent) { + destroyed = httpSessionEvent.getSession().getId(); + } + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/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 3751ef0..384e254 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 @@ -19,30 +19,13 @@ package org.apache.openejb.server.httpd; import org.apache.openejb.assembler.classic.AppInfo; import org.apache.openejb.assembler.classic.Assembler; import org.apache.openejb.assembler.classic.WebAppInfo; +import org.apache.openejb.core.WebContext; import org.apache.openejb.loader.SystemInstance; +import org.apache.openejb.server.httpd.session.SessionManager; 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; -import javax.servlet.AsyncContext; -import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletRequestEvent; -import javax.servlet.ServletRequestListener; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSessionEvent; -import javax.servlet.http.HttpUpgradeHandler; -import javax.servlet.http.Part; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataInput; @@ -67,11 +50,23 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import javax.security.auth.login.LoginException; +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; import static java.util.Collections.singletonList; @@ -83,11 +78,6 @@ public class HttpRequestImpl implements HttpRequest { private static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; private static final String TRANSFER_ENCODING = "Transfer-Encoding"; private static final String CHUNKED = "chunked"; - protected static final String EJBSESSIONID = "EJBSESSIONID"; - protected static final String JSESSIONID = "JSESSIONID"; - - // note: no eviction so invalidate has to be called properly - 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() { @@ -97,43 +87,9 @@ public class HttpRequestImpl implements HttpRequest { } }; - private static volatile ScheduledExecutorService es; - - public static void destroyEviction() { - if (es == null) { - return; - } - es.shutdownNow(); - SESSIONS.clear(); - } - - public static void initEviction() { - 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 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.begin != null) { - data.request.begin.sessionDestroyed(new HttpSessionEvent(session)); - } - } - } - } - }, duration.getTime(), duration.getTime(), duration.getUnit()); - } - private EndWebBeansListener end; private BeginWebBeansListener begin; + private WebContext application; /** * 5.1.1 Method @@ -438,11 +394,13 @@ public class HttpRequestImpl implements HttpRequest { for (String c : cookies) { final String current = c.trim(); if (current.startsWith("EJBSESSIONID=")) { - final RequestSession requestSession = SESSIONS.get(current.substring("EJBSESSIONID=".length())); - session = requestSession == null ? null : requestSession.session; + final SessionManager.SessionWrapper sessionWrapper = + SystemInstance.get().getComponent(SessionManager.class).findSession(current.substring("EJBSESSIONID=".length())); + session = sessionWrapper == null ? null : sessionWrapper.session; } else if (current.startsWith("JSESSIONID=")) { - final RequestSession requestSession = SESSIONS.get(current.substring("JSESSIONID=".length())); - session = requestSession == null ? null : requestSession.session; + final SessionManager.SessionWrapper sessionWrapper = + SystemInstance.get().getComponent(SessionManager.class).findSession(current.substring("JSESSIONID=".length())); + session = sessionWrapper == null ? null : sessionWrapper.session; } } } @@ -929,7 +887,7 @@ public class HttpRequestImpl implements HttpRequest { } } - final HttpSessionImpl impl = new HttpSessionImpl(SESSIONS, contextPath, timeout); + final HttpSessionImpl impl = new HttpSessionImpl(contextPath, timeout); session = impl; if (begin != null) { begin.sessionCreated(new HttpSessionEvent(session)); @@ -937,17 +895,10 @@ public class HttpRequestImpl implements HttpRequest { } impl.callListeners(); // can call req.getSession() so do it after affectation + do it after cdi init - final RequestSession previous = SESSIONS.putIfAbsent(session.getId(), new RequestSession(this, session)); + final SessionManager sessionManager = SystemInstance.get().getComponent(SessionManager.class); + final SessionManager.SessionWrapper previous = sessionManager.newSession(begin, session, application); if (previous != null) { session = previous.session; - } else { - if (es == null) { - synchronized (HttpRequestImpl.class) { - if (es == null) { - initEviction(); - } - } - } } } return session; @@ -1219,6 +1170,10 @@ public class HttpRequestImpl implements HttpRequest { } } + public void setApplication(final WebContext app) { + this.application = app; + } + public void setBeginListener(final BeginWebBeansListener begin) { if (this.begin == null) { this.begin = begin; @@ -1267,7 +1222,7 @@ public class HttpRequestImpl implements HttpRequest { @Override public void invalidate() { - SESSIONS.remove(session.getId()); + SystemInstance.get().getComponent(SessionManager.class).removeSession(session.getId()); try { super.invalidate(); } finally { @@ -1314,15 +1269,4 @@ 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/5af485d0/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpResponseImpl.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpResponseImpl.java b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpResponseImpl.java index 9692e28..180e3e2 100644 --- a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpResponseImpl.java +++ b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/HttpResponseImpl.java @@ -17,6 +17,7 @@ package org.apache.openejb.server.httpd; import org.apache.openejb.loader.SystemInstance; +import org.apache.openejb.server.httpd.session.SessionManager; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.OpenEjbVersion; @@ -483,7 +484,7 @@ public class HttpResponseImpl implements HttpResponse { return; } - headers.put(HttpRequest.HEADER_SET_COOKIE, HttpRequestImpl.EJBSESSIONID + '=' + session.getId() + "; Path=/"); + headers.put(HttpRequest.HEADER_SET_COOKIE, SessionManager.EJBSESSIONID + '=' + session.getId() + "; Path=/"); } /** http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/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 cd0a394..657a394 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 @@ -18,11 +18,8 @@ package org.apache.openejb.server.httpd; import org.apache.openejb.client.ArrayEnumeration; import org.apache.openejb.loader.SystemInstance; +import org.apache.openejb.server.httpd.session.SessionManager; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpSessionContext; -import javax.servlet.http.HttpSessionEvent; -import javax.servlet.http.HttpSessionListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -31,19 +28,20 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentMap; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSessionContext; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; 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, ? extends HttpSessionEvent> mapToClean; private final long created = System.currentTimeMillis(); private volatile long timeout; private volatile long lastAccessed = created; - public HttpSessionImpl(final ConcurrentMap<String, ? extends HttpSessionEvent> sessions, final String contextPath, final long timeout) { - this.mapToClean = sessions; + public HttpSessionImpl(final String contextPath, final long timeout) { this.timeout = timeout; if (contextPath == null) { return; @@ -62,7 +60,7 @@ public class HttpSessionImpl implements HttpSession { } public HttpSessionImpl() { - this(null, null, 30000); + this(null, 30000); } public void newSessionId() { @@ -105,8 +103,9 @@ public class HttpSessionImpl implements HttpSession { } attributes.clear(); - if (mapToClean != null) { - mapToClean.remove(sessionId); + final SessionManager sessionManager = SystemInstance.get().getComponent(SessionManager.class); + if (sessionManager != null) { + sessionManager.removeSession(sessionId); } } @@ -186,16 +185,17 @@ public class HttpSessionImpl implements HttpSession { @Override public HttpSessionContext getSessionContext() { touch(); + final SessionManager component = SystemInstance.get().getComponent(SessionManager.class); return new HttpSessionContext() { @Override public javax.servlet.http.HttpSession getSession(final String sessionId) { - final HttpSessionEvent event = mapToClean.get(sessionId); + final HttpSessionEvent event = component.findSession(sessionId); return event == null ? null : event.getSession(); } @Override public Enumeration<String> getIds() { - return Collections.enumeration(mapToClean.keySet()); + return Collections.enumeration(component.findSessionIds()); } }; } http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpRegistry.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpRegistry.java b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpRegistry.java index 3a61442..6dd8d9e 100644 --- a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpRegistry.java +++ b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpRegistry.java @@ -113,6 +113,8 @@ public class OpenEJBHttpRegistry { final HttpRequestImpl httpRequest = HttpRequestImpl.class.cast(request); final WebContext web = findWebContext(request.getURI() == null ? request.getContextPath() : request.getURI().getPath()); if (web != null) { + httpRequest.setApplication(web); + if (web.getClassLoader() != null) { thread.setContextClassLoader(web.getClassLoader()); } else if (web.getAppContext().getClassLoader() != null) { http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpServer.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpServer.java b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpServer.java index 33f358c..65e8c66 100644 --- a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpServer.java +++ b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/OpenEJBHttpServer.java @@ -22,6 +22,7 @@ import org.apache.openejb.loader.Options; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.server.ServiceException; import org.apache.openejb.server.context.RequestInfos; +import org.apache.openejb.server.httpd.session.SessionManager; import org.apache.openejb.server.stream.CountingInputStream; import org.apache.openejb.server.stream.CountingOutputStream; import org.apache.openejb.util.LogCategory; @@ -76,6 +77,9 @@ public class OpenEJBHttpServer implements HttpServer { public OpenEJBHttpServer(final HttpListener listener) { this.listener = new OpenEJBHttpRegistry.ClassLoaderHttpListener(listener, ParentClassLoaderFinder.Helper.get()); + if (SystemInstance.get().getComponent(SessionManager.class) == null) { + SystemInstance.get().setComponent(SessionManager.class, new SessionManager()); + } } public static boolean isTextXml(final Map<String, String> headers) { @@ -170,7 +174,10 @@ public class OpenEJBHttpServer implements HttpServer { @Override public void stop() throws ServiceException { OpenEJBAsyncContext.destroy(); - HttpRequestImpl.destroyEviction(); + final SessionManager component = SystemInstance.get().getComponent(SessionManager.class); + if (component != null) { + component.destroy(); + } } @Override http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/session/SessionManager.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/session/SessionManager.java b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/session/SessionManager.java new file mode 100644 index 0000000..ad455b1 --- /dev/null +++ b/server/openejb-http/src/main/java/org/apache/openejb/server/httpd/session/SessionManager.java @@ -0,0 +1,132 @@ +/** + * 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.server.httpd.session; + +import org.apache.openejb.core.WebContext; +import org.apache.openejb.loader.SystemInstance; +import org.apache.openejb.server.httpd.BeginWebBeansListener; +import org.apache.openejb.server.httpd.HttpRequestImpl; +import org.apache.openejb.server.httpd.HttpSession; +import org.apache.openejb.util.DaemonThreadFactory; +import org.apache.openejb.util.Duration; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpSessionEvent; + +public class SessionManager { + public static final String EJBSESSIONID = "EJBSESSIONID"; + public static final String JSESSIONID = "JSESSIONID"; + + private final ConcurrentMap<String, SessionWrapper> sessions = new ConcurrentHashMap<>(); + + private static volatile ScheduledExecutorService es; + + public void destroy(final WebContext app) { + final Iterator<SessionWrapper> iterator = sessions.values().iterator(); + while (iterator.hasNext()) { + final SessionWrapper next = iterator.next(); + if (next.app == app) { + next.session.invalidate(); + iterator.remove(); + } + } + } + + public void destroy() { + if (es == null) { + return; + } + es.shutdownNow(); + for (final SessionWrapper rs : sessions.values()) { + rs.session.invalidate(); + } + sessions.clear(); + } + + public void initEviction() { + 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 SessionWrapper 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.listener != null) { + data.listener.sessionDestroyed(new HttpSessionEvent(session)); + } + } + } + } + }, duration.getTime(), duration.getTime(), duration.getUnit()); + } + + public SessionWrapper findSession(final String id) { + return sessions.get(id); + } + + public void removeSession(final String sessionId) { + sessions.remove(sessionId); + } + + public Collection<String> findSessionIds() { + return sessions.keySet(); + } + + public int size() { + return sessions.size(); + } + + public SessionWrapper newSession(final BeginWebBeansListener listener, final HttpSession session, final WebContext app) { + final SessionWrapper existing = sessions.putIfAbsent(session.getId(), new SessionWrapper(listener, session, app)); + if (existing == null && es == null) { + synchronized (this) { + if (es == null) { + initEviction(); + } + } + } + return existing; + } + + public static class SessionWrapper extends HttpSessionEvent { + public final BeginWebBeansListener listener; + public final HttpSession session; + public final WebContext app; + + public SessionWrapper(final BeginWebBeansListener listener, final HttpSession session, final WebContext app) { + super(session); + this.listener = listener; + this.session = session; + this.app = app; + } + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/5af485d0/server/openejb-http/src/test/java/org/apache/openejb/server/httpd/HttpResponseImplSessionTest.java ---------------------------------------------------------------------- diff --git a/server/openejb-http/src/test/java/org/apache/openejb/server/httpd/HttpResponseImplSessionTest.java b/server/openejb-http/src/test/java/org/apache/openejb/server/httpd/HttpResponseImplSessionTest.java index 595c9be..b43d130 100644 --- a/server/openejb-http/src/test/java/org/apache/openejb/server/httpd/HttpResponseImplSessionTest.java +++ b/server/openejb-http/src/test/java/org/apache/openejb/server/httpd/HttpResponseImplSessionTest.java @@ -21,41 +21,31 @@ import org.apache.openejb.core.CoreContainerSystem; import org.apache.openejb.core.ivm.naming.IvmJndiFactory; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.server.ServiceException; +import org.apache.openejb.server.httpd.session.SessionManager; import org.apache.openejb.spi.ContainerSystem; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; -import javax.servlet.http.HttpSession; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.reflect.Field; import java.net.Socket; -import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.http.HttpSession; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class HttpResponseImplSessionTest { - private static Field sessions; - private OpenEJBHttpEjbServer server; - @BeforeClass - public static void findSessionsField() throws NoSuchFieldException { - sessions = HttpRequestImpl.class.getDeclaredField("SESSIONS"); - sessions.setAccessible(true); - } - private static int numberOfSessions() throws IllegalAccessException { - return Map.class.cast(sessions.get(null)).size(); + return SystemInstance.get().getComponent(SessionManager.class).size(); } @Before