diff options
author | shuyangzhou | 2017-05-18 20:58:10 +0000 |
---|---|---|
committer | Raymond Auge | 2018-01-22 15:45:43 +0000 |
commit | 5951364be6642087127fd0ea77b39a70017223b9 (patch) | |
tree | 6104bd38c33617daca9933ce2c4394e41d8d5f31 | |
parent | da22ae61807be63939ff24057ee2b609c2cc0226 (diff) | |
download | rt.equinox.bundles-5951364be6642087127fd0ea77b39a70017223b9.tar.gz rt.equinox.bundles-5951364be6642087127fd0ea77b39a70017223b9.tar.xz rt.equinox.bundles-5951364be6642087127fd0ea77b39a70017223b9.zip |
Bug 530063 - [http] CNFE when session replication is used with equinox.http.servlet in bridge mode
Signed-off-by: Raymond Auge <raymond.auge@liferay.com>
Signed-off-by: shuyangzhou <shuyang.zhou@liferay.com>
Change-Id: I43a34e344c77bfae15d80bbcdb8fc73dd5b56734
12 files changed, 459 insertions, 188 deletions
diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF index 2205f8b17..30d52b5d6 100644 --- a/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.equinox.http.servlet.tests Bundle-SymbolicName: org.eclipse.equinox.http.servlet.tests -Bundle-Version: 1.4.100.qualifier +Bundle-Version: 1.5.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Eclipse-BundleShape: dir Bundle-Activator: org.eclipse.equinox.http.servlet.tests.bundle.Activator @@ -14,6 +14,7 @@ Import-Package: javax.servlet;version="2.6.0", org.apache.commons.fileupload.servlet;version="1.2.2", org.eclipse.equinox.http.servlet;version="1.1.0", org.eclipse.equinox.http.servlet.context;version="1.0.0", + org.eclipse.equinox.http.servlet.session;version="1.0.0", org.eclipse.osgi.service.urlconversion;version="1.0.0", org.osgi.framework;version="1.6.0", org.osgi.framework.hooks.service;version="1.1.0", diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/pom.xml b/bundles/org.eclipse.equinox.http.servlet.tests/pom.xml index 9d8701621..7810450a7 100644 --- a/bundles/org.eclipse.equinox.http.servlet.tests/pom.xml +++ b/bundles/org.eclipse.equinox.http.servlet.tests/pom.xml @@ -19,7 +19,7 @@ </parent> <groupId>org.eclipse.equinox</groupId> <artifactId>org.eclipse.equinox.http.servlet.tests</artifactId> - <version>1.4.100-SNAPSHOT</version> + <version>1.5.0-SNAPSHOT</version> <packaging>eclipse-test-plugin</packaging> <build> diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java index 11ee568b5..e1d3cef73 100644 --- a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java +++ b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java @@ -77,6 +77,7 @@ import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.eclipse.equinox.http.servlet.ExtendedHttpService; import org.eclipse.equinox.http.servlet.context.ContextPathCustomizer; +import org.eclipse.equinox.http.servlet.session.HttpSessionInvalidator; import org.eclipse.equinox.http.servlet.testbase.BaseTest; import org.eclipse.equinox.http.servlet.tests.util.BaseAsyncServlet; import org.eclipse.equinox.http.servlet.tests.util.BaseChangeSessionIdServlet; @@ -109,6 +110,7 @@ import org.osgi.service.http.runtime.dto.RuntimeDTO; import org.osgi.service.http.runtime.dto.ServletContextDTO; import org.osgi.service.http.runtime.dto.ServletDTO; import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; +import org.osgi.util.tracker.ServiceTracker; public class ServletTest extends BaseTest { @Rule @@ -1578,6 +1580,130 @@ public class ServletTest extends BaseTest { } @Test + public void test_Sessions03_HttpSessionInvalidator() throws Exception { + ServiceTracker<HttpSessionInvalidator, HttpSessionInvalidator> sessionInvalidatorTracker = + new ServiceTracker<>(getBundleContext(), HttpSessionInvalidator.class, null); + sessionInvalidatorTracker.open(); + HttpSessionInvalidator invalidator = sessionInvalidatorTracker.waitForService(100); + + final AtomicBoolean valueBound = new AtomicBoolean(false); + final AtomicBoolean valueUnbound = new AtomicBoolean(false); + final HttpSessionBindingListener bindingListener = new HttpSessionBindingListener() { + + @Override + public void valueUnbound(HttpSessionBindingEvent event) { + valueUnbound.set(true); + } + + @Override + public void valueBound(HttpSessionBindingEvent event) { + valueBound.set(true); + } + }; + final AtomicBoolean sessionCreated = new AtomicBoolean(false); + final AtomicBoolean sessionDestroyed = new AtomicBoolean(false); + final AtomicReference<String> sessionId = new AtomicReference<String>(); + HttpSessionListener sessionListener = new HttpSessionListener() { + + @Override + public void sessionDestroyed(HttpSessionEvent se) { + sessionDestroyed.set(true); + } + + @Override + public void sessionCreated(HttpSessionEvent se) { + sessionCreated.set(true); + } + }; + HttpServlet sessionServlet = new HttpServlet() { + private static final long serialVersionUID = 1L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, + IOException { + HttpSession session = request.getSession(); + if (session.getAttribute("test.attribute") == null) { + session.setAttribute("test.attribute", bindingListener); + sessionId.set(session.getId()); + response.getWriter().print("created"); + } else { + session.invalidate(); + response.getWriter().print("invalidated"); + } + } + + }; + ServiceRegistration<Servlet> servletReg = null; + ServiceRegistration<HttpSessionListener> sessionListenerReg = null; + Dictionary<String, Object> servletProps = new Hashtable<String, Object>(); + servletProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/sessions"); + String actual = null; + CookieHandler previous = CookieHandler.getDefault(); + CookieHandler.setDefault(new CookieManager( null, CookiePolicy.ACCEPT_ALL ) ); + try { + servletReg = getBundleContext().registerService(Servlet.class, sessionServlet, servletProps); + Dictionary<String, String> listenerProps = new Hashtable<String, String>(); + listenerProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_LISTENER, "true"); + sessionListenerReg = getBundleContext().registerService(HttpSessionListener.class, sessionListener, listenerProps); + + sessionCreated.set(false); + valueBound.set(false); + sessionDestroyed.set(false); + valueUnbound.set(false); + + // first call will create the session + actual = requestAdvisor.request("sessions"); + assertEquals("Wrong result", "created", actual); + assertTrue("No sessionCreated called", sessionCreated.get()); + assertTrue("No valueBound called", valueBound.get()); + assertFalse("sessionDestroyed was called", sessionDestroyed.get()); + assertFalse("valueUnbound was called", valueUnbound.get()); + + sessionCreated.set(false); + valueBound.set(false); + sessionDestroyed.set(false); + valueUnbound.set(false); + + assertNotNull(sessionId.get()); + + // invalidate using the invalidator + invalidator.invalidate(sessionId.get(), true); + + // second call should find the session invalidated, and create a new one + actual = requestAdvisor.request("sessions"); + assertEquals("Wrong result", "created", actual); + assertTrue("No sessionCreated was called", sessionCreated.get()); + assertTrue("No valueBound was called", valueBound.get()); + assertTrue("No sessionDestroyed called", sessionDestroyed.get()); + assertTrue("No valueUnbound called", valueUnbound.get()); + + sessionCreated.set(false); + sessionDestroyed.set(false); + valueBound.set(false); + valueUnbound.set(false); + + // calling again should invalidate the session again + actual = requestAdvisor.request("sessions"); + assertEquals("Wrong result", "invalidated", actual); + assertFalse("sessionCreated called", sessionCreated.get()); + assertFalse("valueBound called", valueBound.get()); + assertTrue("No sessionDestroyed called", sessionDestroyed.get()); + assertTrue("No valueUnbound called", valueUnbound.get()); + } catch (Exception e) { + fail("Unexpected exception: " + e); + } finally { + if (servletReg != null) { + servletReg.unregister(); + } + if (sessionListenerReg != null) { + sessionListenerReg.unregister(); + } + CookieHandler.setDefault(previous); + sessionInvalidatorTracker.close(); + } + } + + @Test public void test_Resource1() throws Exception { String expected = "a"; String actual; @@ -3160,11 +3286,11 @@ public class ServletTest extends BaseTest { final AtomicReference<HttpSession> sessionReference = new AtomicReference<HttpSession>(); ServletContextListener scl = new ServletContextListener() { - + @Override public void contextInitialized(ServletContextEvent arg0) { } - + @Override public void contextDestroyed(ServletContextEvent arg0) { listenerBalance.decrementAndGet(); diff --git a/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF index 015997743..8802c72f9 100644 --- a/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF @@ -3,12 +3,13 @@ Bundle-ManifestVersion: 2 Bundle-Name: %bundleName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.equinox.http.servlet -Bundle-Version: 1.4.100.qualifier +Bundle-Version: 1.5.0.qualifier Bundle-Activator: org.eclipse.equinox.http.servlet.internal.Activator Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Export-Package: org.eclipse.equinox.http.servlet;version="1.2.0", org.eclipse.equinox.http.servlet.context;version="1.0.0";x-internal:=true, + org.eclipse.equinox.http.servlet.session;version="1.0.0";x-internal:=true, org.eclipse.equinox.http.servlet.dto;version="1.0.0";x-internal:=true Import-Package: org.apache.commons.fileupload;version="[1.2.2, 2.0.0)";resolution:=optional, org.apache.commons.fileupload.disk;version="[1.2.2, 2.0.0)";resolution:=optional, diff --git a/bundles/org.eclipse.equinox.http.servlet/pom.xml b/bundles/org.eclipse.equinox.http.servlet/pom.xml index 8446a6712..1462163d0 100644 --- a/bundles/org.eclipse.equinox.http.servlet/pom.xml +++ b/bundles/org.eclipse.equinox.http.servlet/pom.xml @@ -20,6 +20,6 @@ </parent> <groupId>org.eclipse.equinox</groupId> <artifactId>org.eclipse.equinox.http.servlet</artifactId> - <version>1.4.100-SNAPSHOT</version> + <version>1.5.0-SNAPSHOT</version> <packaging>eclipse-plugin</packaging> </project> diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/Activator.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/Activator.java index 8781c901f..301b1fe0f 100644 --- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/Activator.java +++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/Activator.java @@ -163,8 +163,7 @@ public class Activator boolean useSystemContext = Boolean.valueOf(context.getProperty(PROP_GLOBAL_WHITEBOARD)); BundleContext trackingContext = useSystemContext ? context.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).getBundleContext() : context; HttpServiceRuntimeImpl httpServiceRuntime = new HttpServiceRuntimeImpl( - trackingContext, context, servletContext, - new UMDictionaryMap<String, Object>(serviceProperties)); + trackingContext, context, servletContext, serviceProperties); proxyServlet.setHttpServiceRuntimeImpl(httpServiceRuntime); diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java index 62474bcef..c25aa85eb 100644 --- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java +++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java @@ -25,8 +25,10 @@ import org.eclipse.equinox.http.servlet.context.ContextPathCustomizer; import org.eclipse.equinox.http.servlet.dto.ExtendedFailedServletDTO; import org.eclipse.equinox.http.servlet.internal.context.*; import org.eclipse.equinox.http.servlet.internal.error.*; +import org.eclipse.equinox.http.servlet.internal.servlet.HttpSessionTracker; import org.eclipse.equinox.http.servlet.internal.servlet.Match; import org.eclipse.equinox.http.servlet.internal.util.*; +import org.eclipse.equinox.http.servlet.session.HttpSessionInvalidator; import org.osgi.framework.*; import org.osgi.framework.dto.ServiceReferenceDTO; import org.osgi.service.http.HttpContext; @@ -49,7 +51,7 @@ public class HttpServiceRuntimeImpl public HttpServiceRuntimeImpl( BundleContext trackingContext, BundleContext consumingContext, - ServletContext parentServletContext, Map<String, Object> attributes) { + ServletContext parentServletContext, Dictionary<String, Object> attributes) { this.trackingContext = trackingContext; this.consumingContext = consumingContext; @@ -60,8 +62,10 @@ public class HttpServiceRuntimeImpl this.listenerServiceFilter = createListenerFilter(consumingContext, parentServletContext); this.parentServletContext = parentServletContext; - this.attributes = attributes; - this.targetFilter = "(" + Activator.UNIQUE_SERVICE_ID + "=" + attributes.get(Activator.UNIQUE_SERVICE_ID) + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + this.attributes = new UMDictionaryMap<String, Object>(attributes); + this.targetFilter = "(" + Activator.UNIQUE_SERVICE_ID + "=" + this.attributes.get(Activator.UNIQUE_SERVICE_ID) + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + this.httpSessionTracker = new HttpSessionTracker(this); + this.invalidatorReg = trackingContext.registerService(HttpSessionInvalidator.class, this.httpSessionTracker, attributes); contextServiceTracker = new ServiceTracker<ServletContextHelper, AtomicReference<ContextController>>( @@ -111,7 +115,7 @@ public class HttpServiceRuntimeImpl ContextController contextController = new ContextController( trackingContext, consumingContext, serviceReference, new ProxyContext(parentServletContext), - this, contextName, contextPath); + this, contextName, contextPath, httpSessionTracker); controllerMap.put(serviceReference, contextController); @@ -174,6 +178,7 @@ public class HttpServiceRuntimeImpl } public void destroy() { + invalidatorReg.unregister(); defaultContextReg.unregister(); contextServiceTracker.close(); @@ -188,6 +193,9 @@ public class HttpServiceRuntimeImpl failedServletContextDTOs.clear(); failedServletDTOs.clear(); + httpSessionTracker.clear(); + + httpSessionTracker = null; attributes = null; trackingContext = null; consumingContext = null; @@ -276,6 +284,10 @@ public class HttpServiceRuntimeImpl return null; } + public void log(String message) { + parentServletContext.log(message); + } + public void log(String message, Throwable t) { parentServletContext.log(message, t); } @@ -1104,6 +1116,8 @@ public class HttpServiceRuntimeImpl private ServiceTracker<ServletContextHelper, AtomicReference<ContextController>> contextServiceTracker; private ServiceTracker<ContextPathCustomizer, ContextPathCustomizer> contextPathAdaptorTracker; private ContextPathCustomizerHolder contextPathCustomizerHolder; + private HttpSessionTracker httpSessionTracker; + private final ServiceRegistration<HttpSessionInvalidator> invalidatorReg; static class DefaultServletContextHelperFactory implements ServiceFactory<ServletContextHelper> { @Override diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java index 019c6a3fb..90f50d520 100644 --- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java +++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java @@ -110,7 +110,7 @@ public class ContextController { BundleContext trackingContextParam, BundleContext consumingContext, ServiceReference<ServletContextHelper> servletContextHelperRef, ProxyContext proxyContext, HttpServiceRuntimeImpl httpServiceRuntime, - String contextName, String contextPath) { + String contextName, String contextPath, HttpSessionTracker httpSessionTracker) { validate(contextName, contextPath); @@ -141,6 +141,7 @@ public class ContextController { this.trackingContext = trackingContextParam; this.consumingContext = consumingContext; + this.httpSessionTracker = httpSessionTracker; listenerServiceTracker = new ServiceTracker<EventListener, AtomicReference<ListenerRegistration>>( trackingContext, httpServiceRuntime.getListenerFilter(), @@ -452,7 +453,7 @@ public class ContextController { } finally { if (registration == null) { // Always attempt to release here; even though destroy() may have been called - // on the registration while failing to add. There are cases where no + // on the registration while failing to add. There are cases where no // ServletRegistration may have even been created at all to call destory() on. // Also, addedRegisteredObject may be false which means we never call doAddServletRegistration servletHolder.release(); @@ -1208,7 +1209,15 @@ public class ContextController { } public void removeActiveSession(HttpSession session) { - activeSessions.remove(session.getId()); + removeActiveSession(session.getId()); + } + + public void removeActiveSession(String sessionId) { + HttpSessionAdaptor httpSessionAdaptor = activeSessions.remove(sessionId); + + if (httpSessionAdaptor != null) { + httpSessionTracker.removeHttpSessionAdaptor(sessionId, httpSessionAdaptor); + } } public void fireSessionIdChanged(String oldSessionId) { @@ -1246,7 +1255,7 @@ public class ContextController { session, servletContext, this); HttpSessionAdaptor previousHttpSessionAdaptor = - activeSessions.putIfAbsent(sessionId, httpSessionAdaptor); + addSessionAdaptor(sessionId, httpSessionAdaptor); if (previousHttpSessionAdaptor != null) { return previousHttpSessionAdaptor; @@ -1268,6 +1277,21 @@ public class ContextController { return httpSessionAdaptor; } + public HttpSessionAdaptor addSessionAdaptor( + String sessionId, HttpSessionAdaptor httpSessionAdaptor) { + + HttpSessionAdaptor previousHttpSessionAdaptor = + activeSessions.putIfAbsent(sessionId, httpSessionAdaptor); + + if (previousHttpSessionAdaptor != null) { + return previousHttpSessionAdaptor; + } + + httpSessionTracker.addHttpSessionAdaptor(sessionId, httpSessionAdaptor); + + return null; + } + private void validate(String preValidationContextName, String preValidationContextPath) { if (!contextNamePattern.matcher(preValidationContextName).matches()) { throw new IllegalContextNameException( @@ -1305,6 +1329,7 @@ public class ContextController { private final ConcurrentMap<String, HttpSessionAdaptor> activeSessions = new ConcurrentHashMap<String, HttpSessionAdaptor>(); private final HttpServiceRuntimeImpl httpServiceRuntime; + private final HttpSessionTracker httpSessionTracker; private final Set<ListenerRegistration> listenerRegistrations = new HashSet<ListenerRegistration>(); private final ProxyContext proxyContext; private final ServiceReference<ServletContextHelper> servletContextHelperRef; diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletRequestWrapperImpl.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletRequestWrapperImpl.java index e7c05f197..dd8bb5524 100644 --- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletRequestWrapperImpl.java +++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpServletRequestWrapperImpl.java @@ -73,6 +73,25 @@ public class HttpServletRequestWrapperImpl extends HttpServletRequestWrapper { this.request = request; } + @Override + public String changeSessionId() { + HttpSessionAdaptor httpSessionAdaptor = (HttpSessionAdaptor)getSession(false); + + if (httpSessionAdaptor == null) { + throw new IllegalStateException("No session"); //$NON-NLS-1$ + } + + DispatchTargets currentDispatchTarget = dispatchTargets.peek(); + + String oldSessionId = httpSessionAdaptor.getId(); + String newSessionId = super.changeSessionId(); + + currentDispatchTarget.getContextController().removeActiveSession(oldSessionId); + currentDispatchTarget.getContextController().addSessionAdaptor(newSessionId, httpSessionAdaptor); + + return newSessionId; + } + public String getAuthType() { String authType = (String) this.getAttribute(HttpContext.AUTHENTICATION_TYPE); if (authType != null) @@ -172,7 +191,7 @@ public class HttpServletRequestWrapperImpl extends HttpServletRequestWrapper { if ((dispatcherType == DispatcherType.ASYNC) || (dispatcherType == DispatcherType.REQUEST) || - !attributeName.startsWith("javax.servlet.")) { + !attributeName.startsWith("javax.servlet.")) { //$NON-NLS-1$ return request.getAttribute(attributeName); } @@ -239,7 +258,7 @@ public class HttpServletRequestWrapperImpl extends HttpServletRequestWrapper { } } else if (dispatcherType == DispatcherType.FORWARD) { - if (hasServletName && attributeName.startsWith("javax.servlet.forward")) { + if (hasServletName && attributeName.startsWith("javax.servlet.forward")) { //$NON-NLS-1$ return null; } diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpSessionAdaptor.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpSessionAdaptor.java index c53578bfc..eb6112f40 100644 --- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpSessionAdaptor.java +++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpSessionAdaptor.java @@ -14,166 +14,14 @@ package org.eclipse.equinox.http.servlet.internal.servlet; import java.io.Serializable; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletContext; import javax.servlet.http.*; import org.eclipse.equinox.http.servlet.internal.context.ContextController; -import org.eclipse.equinox.http.servlet.internal.util.EventListeners; // This class adapts HttpSessions in order to return the right ServletContext and attributes public class HttpSessionAdaptor implements HttpSession, Serializable { private static final long serialVersionUID = 3418610936889860782L; - static class ParentSessionListener implements HttpSessionBindingListener, Serializable { - private static final long serialVersionUID = 4626167646903550760L; - - private static final String PARENT_SESSION_LISTENER_KEY = "org.eclipse.equinox.http.parent.session.listener"; //$NON-NLS-1$ - transient final Set<HttpSessionAdaptor> innerSessions = Collections.newSetFromMap(new ConcurrentHashMap<HttpSessionAdaptor, Boolean>()); - @Override - public void valueBound(HttpSessionBindingEvent event) { - // do nothing - } - - @Override - public void valueUnbound(HttpSessionBindingEvent event) { - // Here we assume the unbound event is signifying the session is being invalidated. - // Must invalidate the inner sessions - Iterator<HttpSessionAdaptor> iterator = innerSessions.iterator(); - - while (iterator.hasNext()) { - HttpSessionAdaptor innerSession = iterator.next(); - - iterator.remove(); - - ContextController contextController = - innerSession.getController(); - - EventListeners eventListeners = - contextController.getEventListeners(); - - List<HttpSessionListener> httpSessionListeners = - eventListeners.get(HttpSessionListener.class); - - if (!httpSessionListeners.isEmpty()) { - HttpSessionEvent httpSessionEvent = new HttpSessionEvent( - innerSession); - - for (HttpSessionListener listener : httpSessionListeners) { - try { - listener.sessionDestroyed(httpSessionEvent); - } - catch (IllegalStateException ise) { - // outer session is already invalidated - } - } - } - - contextController.removeActiveSession( - innerSession.getSession()); - } - } - - static void addHttpSessionAdaptor(HttpSessionAdaptor innerSession) { - HttpSession httpSession = innerSession.getSession(); - - ParentSessionListener parentListener; - // need to have a global lock here because we must ensure that this is added only once - synchronized (httpSession) { - parentListener = (ParentSessionListener) httpSession.getAttribute(PARENT_SESSION_LISTENER_KEY); - if (parentListener == null) { - parentListener = new ParentSessionListener(); - httpSession.setAttribute(PARENT_SESSION_LISTENER_KEY, parentListener); - } - } - - parentListener.innerSessions.add(innerSession); - } - - static void removeHttpSessionAdaptor(HttpSessionAdaptor innerSession) { - HttpSession httpSession = innerSession.getSession(); - - ParentSessionListener parentListener = (ParentSessionListener) httpSession.getAttribute(PARENT_SESSION_LISTENER_KEY); - - if (parentListener != null) { - parentListener.innerSessions.remove(innerSession); - } - } - } - - class HttpSessionAttributeWrapper implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable { - private static final long serialVersionUID = 7945998375225990980L; - - final String name; - final Object value; - final boolean added; - final HttpSessionAdaptor innerSession; - - public HttpSessionAttributeWrapper(HttpSessionAdaptor innerSession, String name, Object value, boolean added) { - this.innerSession = innerSession; - this.name = name; - this.value = value; - this.added = added; - } - - @Override - public void valueBound(HttpSessionBindingEvent event) { - List<HttpSessionAttributeListener> listeners = getEventListeners().get( - HttpSessionAttributeListener.class); - - for (HttpSessionAttributeListener listener : listeners) { - if (added) { - listener.attributeAdded(event); - } - else { - listener.attributeReplaced(event); - } - } - - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(innerSession, name, value)); - } - } - - @Override - public void valueUnbound(HttpSessionBindingEvent event) { - if (!added) { - List<HttpSessionAttributeListener> listeners = getEventListeners().get( - HttpSessionAttributeListener.class); - - for (HttpSessionAttributeListener listener : listeners) { - listener.attributeRemoved(event); - } - } - - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(innerSession, name, value)); - } - } - - @Override - public void sessionWillPassivate(HttpSessionEvent se) { - if (value instanceof HttpSessionActivationListener) { - ((HttpSessionActivationListener) value).sessionWillPassivate(new HttpSessionEvent(innerSession)); - } - } - - @Override - public void sessionDidActivate(HttpSessionEvent se) { - if (value instanceof HttpSessionActivationListener) { - ((HttpSessionActivationListener) value).sessionDidActivate(new HttpSessionEvent(innerSession)); - } - } - - private EventListeners getEventListeners() { - return innerSession.getController().getEventListeners(); - } - - @Override - public String toString() { - return String.valueOf(value); - } - } - private transient final ContextController controller; private transient final HttpSession session; private transient final ServletContext servletContext; @@ -182,9 +30,7 @@ public class HttpSessionAdaptor implements HttpSession, Serializable { static public HttpSessionAdaptor createHttpSessionAdaptor( HttpSession session, ServletContext servletContext, ContextController controller) { - HttpSessionAdaptor sessionAdaptor = new HttpSessionAdaptor(session, servletContext, controller); - ParentSessionListener.addHttpSessionAdaptor(sessionAdaptor); - return sessionAdaptor; + return new HttpSessionAdaptor(session, servletContext, controller); } private HttpSessionAdaptor( @@ -209,11 +55,7 @@ public class HttpSessionAdaptor implements HttpSession, Serializable { } public Object getAttribute(String arg0) { - Object result = session.getAttribute(attributePrefix + arg0); - if (result instanceof HttpSessionAttributeWrapper) { - result = ((HttpSessionAttributeWrapper) result).value; - } - return result; + return session.getAttribute(attributePrefix.concat(arg0)); } public Enumeration<String> getAttributeNames() { @@ -264,14 +106,14 @@ public class HttpSessionAdaptor implements HttpSession, Serializable { // outer session is already invalidated } + controller.removeActiveSession(session); + try { - ParentSessionListener.removeHttpSessionAdaptor(this); + this.getSession().invalidate(); } catch (IllegalStateException ise) { - // outer session is already invalidated + controller.getHttpServiceRuntime().log("Session already invalidated!", ise); //$NON-NLS-1$ } - - controller.removeActiveSession(session); } public void invokeSessionListeners (List<Class<? extends EventListener>> classes, EventListener listener) { @@ -312,7 +154,30 @@ public class HttpSessionAdaptor implements HttpSession, Serializable { } public void removeAttribute(String arg0) { - session.removeAttribute(attributePrefix + arg0); + String newName = attributePrefix.concat(arg0); + + Object value = session.getAttribute(newName); + + session.removeAttribute(newName); + + if (value == null) { + return; + } + + List<HttpSessionAttributeListener> listeners = + controller.getEventListeners().get( + HttpSessionAttributeListener.class); + + if (listeners.isEmpty()) { + return; + } + + HttpSessionBindingEvent httpSessionBindingEvent = + new HttpSessionBindingEvent(this, newName); + + for (HttpSessionAttributeListener listener : listeners) { + listener.attributeRemoved(httpSessionBindingEvent); + } } /**@deprecated*/ @@ -321,13 +186,35 @@ public class HttpSessionAdaptor implements HttpSession, Serializable { } public void setAttribute(String name, Object value) { - Object actualValue = null; + String newName = attributePrefix.concat(name); + + if (value == null) { + session.setAttribute(newName, null); + + return; + } + + boolean added = session.getAttribute(newName) == null; + + session.setAttribute(newName, value); + + List<HttpSessionAttributeListener> listeners = + controller.getEventListeners().get( + HttpSessionAttributeListener.class); + + if (!listeners.isEmpty()) { + HttpSessionBindingEvent httpSessionBindingEvent = + new HttpSessionBindingEvent(this, newName, value); - if (value != null) { - boolean added = (session.getAttribute(attributePrefix + name) == null); - actualValue = new HttpSessionAttributeWrapper(this, name, value, added); + for (HttpSessionAttributeListener listener : listeners) { + if (added) { + listener.attributeAdded(httpSessionBindingEvent); + } + else { + listener.attributeReplaced(httpSessionBindingEvent); + } + } } - session.setAttribute(attributePrefix + name, actualValue); } public void setMaxInactiveInterval(int arg0) { diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpSessionTracker.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpSessionTracker.java new file mode 100644 index 000000000..e1c05f365 --- /dev/null +++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/HttpSessionTracker.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2018 Liferay, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Liferay, Inc. - Bug 530063 - CNFE when session replication + * is used with equinox.http.servlet in bridge mode + ******************************************************************************/ + +package org.eclipse.equinox.http.servlet.internal.servlet; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import javax.servlet.http.*; +import org.eclipse.equinox.http.servlet.internal.HttpServiceRuntimeImpl; +import org.eclipse.equinox.http.servlet.internal.context.ContextController; +import org.eclipse.equinox.http.servlet.internal.util.EventListeners; +import org.eclipse.equinox.http.servlet.session.HttpSessionInvalidator; + +/** + * @since 1.4 + */ +public class HttpSessionTracker implements HttpSessionInvalidator { + + public HttpSessionTracker(HttpServiceRuntimeImpl httpServiceRuntime) { + this.httpServiceRuntime = httpServiceRuntime; + } + + @Override + public void invalidate(String sessionId, boolean invalidateParent) { + Set<HttpSessionAdaptor> httpSessionAdaptors = + httpSessionAdaptorsMap.remove(sessionId); + + if (httpSessionAdaptors == null) { + return; + } + + for (HttpSessionAdaptor httpSessionAdaptor : httpSessionAdaptors) { + ContextController contextController = + httpSessionAdaptor.getController(); + + EventListeners eventListeners = + contextController.getEventListeners(); + + List<HttpSessionListener> httpSessionListeners = eventListeners.get( + HttpSessionListener.class); + + if (!httpSessionListeners.isEmpty()) { + HttpSessionEvent httpSessionEvent = new HttpSessionEvent( + httpSessionAdaptor); + + for (HttpSessionListener listener : httpSessionListeners) { + try { + listener.sessionDestroyed(httpSessionEvent); + } + catch (IllegalStateException ise) { + // outer session is already invalidated + } + } + } + + List<HttpSessionAttributeListener> httpSessionAttributeListeners = + eventListeners.get(HttpSessionAttributeListener.class); + + if (!httpSessionListeners.isEmpty()) { + Enumeration<String> enumeration = + httpSessionAdaptor.getAttributeNames(); + + while (enumeration.hasMoreElements()) { + HttpSessionBindingEvent httpSessionBindingEvent = + new HttpSessionBindingEvent( + httpSessionAdaptor, enumeration.nextElement()); + + for (HttpSessionAttributeListener + httpSessionAttributeListener : + httpSessionAttributeListeners) { + + httpSessionAttributeListener.attributeRemoved( + httpSessionBindingEvent); + } + } + } + + contextController.removeActiveSession( + httpSessionAdaptor.getSession()); + + if (invalidateParent) { + try { + httpSessionAdaptor.getSession().invalidate(); + } + catch (IllegalStateException ise) { + httpServiceRuntime.log( + "Session was already invalidated!", ise); //$NON-NLS-1$ + } + } + } + } + + public void addHttpSessionAdaptor( + String sessionId, HttpSessionAdaptor httpSessionAdaptor) { + + Set<HttpSessionAdaptor> httpSessionAdaptors = + httpSessionAdaptorsMap.get(sessionId); + + if (httpSessionAdaptors == null) { + httpSessionAdaptors = Collections.newSetFromMap( + new ConcurrentHashMap<HttpSessionAdaptor, Boolean>()); + + Set<HttpSessionAdaptor> previousHttpSessionAdaptors = + httpSessionAdaptorsMap.putIfAbsent( + sessionId, httpSessionAdaptors); + + if (previousHttpSessionAdaptors != null) { + httpSessionAdaptors = previousHttpSessionAdaptors; + } + } + + httpSessionAdaptors.add(httpSessionAdaptor); + } + + public void clear() { + // At this point there should be no left over sessions. If + // there are we'll log it because there's some kind of leak. + if (!httpSessionAdaptorsMap.isEmpty()) { + httpServiceRuntime.log( + "There are HttpSessionAdaptors left over. There might be a context or session leak!"); //$NON-NLS-1$ + } + } + + public boolean removeHttpSessionAdaptor( + String sessionId, HttpSessionAdaptor httpSessionAdaptor) { + + Set<HttpSessionAdaptor> httpSessionAdaptors = + httpSessionAdaptorsMap.get(sessionId); + + if (httpSessionAdaptors == null) { + return false; + } + + try { + return httpSessionAdaptors.remove(httpSessionAdaptor); + } + finally { + if (httpSessionAdaptors.isEmpty()) { + httpSessionAdaptorsMap.remove(sessionId, httpSessionAdaptors); + } + } + } + + private final ConcurrentMap<String, Set<HttpSessionAdaptor>> + httpSessionAdaptorsMap = + new ConcurrentHashMap<String, Set<HttpSessionAdaptor>>(); + private final HttpServiceRuntimeImpl httpServiceRuntime; + +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/session/HttpSessionInvalidator.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/session/HttpSessionInvalidator.java new file mode 100644 index 000000000..06c46467b --- /dev/null +++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/session/HttpSessionInvalidator.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2018 Liferay, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Liferay, Inc. - Bug 530063 - CNFE when session replication + * is used with equinox.http.servlet in bridge mode + ******************************************************************************/ + +package org.eclipse.equinox.http.servlet.session; + +/** + * The Http Whiteboard runtime registers a service of this type to + * allow an external actor to invalidate a session. This requirement is typical of + * the bridge deployment scenario where the host may need to control session invalidation. + * <p> + * <b>Note:</b> This class is part of an interim SPI that is still under + * development and expected to change significantly before reaching stability. + * It is being made available at this stage to solicit feedback from pioneering + * adopters on the understanding that any code that uses this SPI will almost certainly + * be broken (repeatedly) as the SPI evolves. + * </p> + * @since 1.4 + */ +public interface HttpSessionInvalidator { + + /** + * Invalidate a session. If no session matching the id is found, nothing happens. + * Optionally attempt to invalidate the parent (container) session. + * + * @param sessionId the session id to invalidate + * @param invalidateParent if true, attempt to invalidate the parent + * (container) session + */ + public void invalidate(String sessionId, boolean invalidateParent); + +} |