Bug 541607 - fixes Eclipse Info center massive memory leak since Photon

Signed-off-by: Raymond Auge <raymond.auge@liferay.com>
Change-Id: I376dbb41fcd8973144aeb18f547ff049ba724ef5
diff --git a/bundles/org.eclipse.equinox.http.jetty/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.http.jetty/META-INF/MANIFEST.MF
index 8969336..71d2dfd 100644
--- a/bundles/org.eclipse.equinox.http.jetty/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.http.jetty/META-INF/MANIFEST.MF
@@ -4,7 +4,7 @@
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
 Bundle-SymbolicName: org.eclipse.equinox.http.jetty
-Bundle-Version: 3.6.200.qualifier
+Bundle-Version: 3.7.0.qualifier
 Bundle-Activator: org.eclipse.equinox.http.jetty.internal.Activator
 Import-Package: javax.servlet;version="[2.6.0,4.0.0)",
  javax.servlet.http;version="[2.6.0,4.0.0)",
diff --git a/bundles/org.eclipse.equinox.http.jetty/pom.xml b/bundles/org.eclipse.equinox.http.jetty/pom.xml
index 55107c4..f240ce7 100644
--- a/bundles/org.eclipse.equinox.http.jetty/pom.xml
+++ b/bundles/org.eclipse.equinox.http.jetty/pom.xml
@@ -21,6 +21,6 @@
   </parent>
   <groupId>org.eclipse.equinox</groupId>
   <artifactId>org.eclipse.equinox.http.jetty</artifactId>
-  <version>3.6.200-SNAPSHOT</version>
+  <version>3.7.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/JettyConstants.java b/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/JettyConstants.java
index 2044bca..e8664d5 100644
--- a/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/JettyConstants.java
+++ b/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/JettyConstants.java
@@ -151,6 +151,11 @@
 	public static final String CONTEXT_SESSIONINACTIVEINTERVAL = "context.sessioninactiveinterval"; //$NON-NLS-1$
 
 	/**
+	 * name="housekeeper.interval" type="Integer"
+	 */
+	public static final String HOUSEKEEPER_INTERVAL = "housekeeper.interval"; //$NON-NLS-1$
+
+	/**
 	 * name="customizer.class" type="String" <br />
 	 * (full qualified name of the class that implements
 	 * <code>org.eclipse.equinox.http.jetty.JettyCustomizer</code> and has a public no-arg constructor;
diff --git a/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/Activator.java b/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/Activator.java
index 10a0f3d..f00174c 100644
--- a/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/Activator.java
+++ b/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/Activator.java
@@ -197,6 +197,16 @@
 			}
 		}
 
+		// House Keeper Interval
+		String houseKeeperInterval = Details.getStringProp(context, JettyConstants.HOUSEKEEPER_INTERVAL, null);
+		if (houseKeeperInterval != null) {
+			try {
+				defaultSettings.put(JettyConstants.HOUSEKEEPER_INTERVAL, Long.valueOf(houseKeeperInterval));
+			} catch (NumberFormatException e) {
+				//(log this) ignore
+			}
+		}
+
 		// Other Info
 		String otherInfo = Details.getStringProp(context, JettyConstants.OTHER_INFO, null);
 		if (otherInfo != null)
diff --git a/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/HttpServerManager.java b/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/HttpServerManager.java
index b4d65a4..ea16184 100644
--- a/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/HttpServerManager.java
+++ b/bundles/org.eclipse.equinox.http.jetty/src/org/eclipse/equinox/http/jetty/internal/HttpServerManager.java
@@ -22,12 +22,12 @@
 import java.lang.reflect.Method;
 import java.util.*;
 import javax.servlet.*;
-import javax.servlet.http.HttpSessionEvent;
-import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.*;
 import org.eclipse.equinox.http.jetty.JettyConstants;
 import org.eclipse.equinox.http.jetty.JettyCustomizer;
 import org.eclipse.equinox.http.servlet.HttpServiceServlet;
 import org.eclipse.jetty.server.*;
+import org.eclipse.jetty.server.session.HouseKeeper;
 import org.eclipse.jetty.server.session.SessionHandler;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
@@ -159,6 +159,9 @@
 
 		try {
 			server.start();
+
+			HouseKeeper houseKeeper = server.getSessionIdManager().getSessionHouseKeeper();
+			houseKeeper.setIntervalSec(Details.getLong(dictionary, JettyConstants.HOUSEKEEPER_INTERVAL, houseKeeper.getIntervalSec()));
 		} catch (Exception e) {
 			throw new ConfigurationException(pid, e.getMessage(), e);
 		}
@@ -250,24 +253,33 @@
 		}
 	}
 
-	public static class InternalHttpServiceServlet implements HttpSessionIdListener, Servlet {
+	public static class InternalHttpServiceServlet implements HttpSessionListener, HttpSessionIdListener, Servlet {
 		//		private static final long serialVersionUID = 7477982882399972088L;
-		private Servlet httpServiceServlet = new HttpServiceServlet();
+		private final Servlet httpServiceServlet = new HttpServiceServlet();
 		private ClassLoader contextLoader;
-		private Method method;
+		private final Method sessionDestroyed;
+		private final Method sessionIdChanged;
+
+		public InternalHttpServiceServlet() {
+			Class<?> clazz = httpServiceServlet.getClass();
+
+			try {
+				sessionDestroyed = clazz.getMethod("sessionDestroyed", new Class<?>[] {String.class}); //$NON-NLS-1$
+			} catch (Exception e) {
+				throw new IllegalStateException(e);
+			}
+			try {
+				sessionIdChanged = clazz.getMethod("sessionIdChanged", new Class<?>[] {String.class}); //$NON-NLS-1$
+			} catch (Exception e) {
+				throw new IllegalStateException(e);
+			}
+		}
 
 		@Override
 		public void init(ServletConfig config) throws ServletException {
 			ServletContext context = config.getServletContext();
 			contextLoader = (ClassLoader) context.getAttribute(INTERNAL_CONTEXT_CLASSLOADER);
 
-			Class<?> clazz = httpServiceServlet.getClass();
-			try {
-				method = clazz.getMethod("sessionIdChanged", new Class<?>[] {String.class}); //$NON-NLS-1$
-			} catch (Exception e) {
-				throw new ServletException(e);
-			}
-
 			Thread thread = Thread.currentThread();
 			ClassLoader current = thread.getContextClassLoader();
 			thread.setContextClassLoader(contextLoader);
@@ -314,12 +326,35 @@
 		}
 
 		@Override
+		public void sessionCreated(HttpSessionEvent event) {
+			// Nothing to do.
+		}
+
+		@Override
+		public void sessionDestroyed(HttpSessionEvent event) {
+			Thread thread = Thread.currentThread();
+			ClassLoader current = thread.getContextClassLoader();
+			thread.setContextClassLoader(contextLoader);
+			try {
+				sessionDestroyed.invoke(httpServiceServlet, event.getSession().getId());
+			} catch (IllegalAccessException e) {
+				// not likely
+			} catch (IllegalArgumentException e) {
+				// not likely
+			} catch (InvocationTargetException e) {
+				throw new RuntimeException(e.getCause());
+			} finally {
+				thread.setContextClassLoader(current);
+			}
+		}
+
+		@Override
 		public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
 			Thread thread = Thread.currentThread();
 			ClassLoader current = thread.getContextClassLoader();
 			thread.setContextClassLoader(contextLoader);
 			try {
-				method.invoke(httpServiceServlet, oldSessionId);
+				sessionIdChanged.invoke(httpServiceServlet, oldSessionId);
 			} catch (IllegalAccessException e) {
 				// not likely
 			} catch (IllegalArgumentException e) {
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 981c2c0..6304f11 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 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: org.eclipse.equinox.http.servlet.tests
 Bundle-SymbolicName: org.eclipse.equinox.http.servlet.tests
-Bundle-Version: 1.5.200.qualifier
+Bundle-Version: 1.5.300.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Eclipse-BundleShape: dir
 Bundle-Activator: org.eclipse.equinox.http.servlet.tests.bundle.Activator
diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/pom.xml b/bundles/org.eclipse.equinox.http.servlet.tests/pom.xml
index 5d1437d..a9b7e11 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.5.200-SNAPSHOT</version>
+  <version>1.5.300-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
 
   <build>
diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/testbase/BaseTest.java b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/testbase/BaseTest.java
index 527492c..59dee05 100644
--- a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/testbase/BaseTest.java
+++ b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/testbase/BaseTest.java
@@ -69,6 +69,8 @@
 		System.setProperty("org.eclipse.jetty.servlet.LEVEL", "OFF");
 
 		System.setProperty("org.osgi.service.http.port", "0");
+		System.setProperty("org.eclipse.equinox.http.jetty.context.sessioninactiveinterval", "1");
+		System.setProperty("org.eclipse.equinox.http.jetty.housekeeper.interval", "10");
 		BundleContext bundleContext = getBundleContext();
 		installer = new BundleInstaller(TEST_BUNDLES_BINARY_DIRECTORY, bundleContext);
 		advisor = new BundleAdvisor(bundleContext);
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 74a8985..daac630 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
@@ -45,6 +45,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -1819,6 +1820,63 @@
 	}
 
 	@Test
+	public void test_Sessions05_Bug541607_MemoryLeak() throws Exception {
+		final List<String> sessionIds = new CopyOnWriteArrayList<>();
+		HttpSessionListener sessionListener = new HttpSessionListener() {
+
+			@Override
+			public void sessionDestroyed(HttpSessionEvent se) {
+				sessionIds.remove(se.getSession().getId());
+			}
+
+			@Override
+			public void sessionCreated(HttpSessionEvent se) {
+				sessionIds.add(se.getSession().getId());
+			}
+		};
+		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();
+				response.getWriter().print("created " + session.getId());
+			}
+
+		};
+		ServiceRegistration<Servlet> servletReg = null;
+		ServiceRegistration<HttpSessionListener> sessionListenerReg = null;
+		Dictionary<String, Object> servletProps = new Hashtable<String, Object>();
+		servletProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/sessions");
+
+		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);
+
+			// call the servet 10 times, we should get 10 sessions
+			for (int i = 0; i < 10; i++) {
+				requestAdvisor.request("sessions");
+			}
+
+			assertEquals("Wrong result", 10, sessionIds.size());
+			Thread.sleep(12000); // 12 seconds
+			assertEquals("Wrong result", 0, sessionIds.size());
+		} catch (Exception e) {
+			fail("Unexpected exception: " + e);
+		} finally {
+			if (servletReg != null) {
+				servletReg.unregister();
+			}
+			if (sessionListenerReg != null) {
+				sessionListenerReg.unregister();
+			}
+		}
+	}
+
+	@Test
 	public void test_Resource1() throws Exception {
 		String expected = "a";
 		String actual;
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 92348f3..d6285ac 100644
--- a/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %bundleName
 Bundle-Vendor: %providerName
 Bundle-SymbolicName: org.eclipse.equinox.http.servlet
-Bundle-Version: 1.5.200.qualifier
+Bundle-Version: 1.5.300.qualifier
 Bundle-Activator: org.eclipse.equinox.http.servlet.internal.Activator
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
diff --git a/bundles/org.eclipse.equinox.http.servlet/pom.xml b/bundles/org.eclipse.equinox.http.servlet/pom.xml
index 9faae4f..a50ce57 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.5.200-SNAPSHOT</version>
+  <version>1.5.300-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
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 44b55ee..1ad919b 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
@@ -1075,6 +1075,10 @@
 		}
 	}
 
+	public void sessionDestroyed(String sessionId) {
+		httpSessionTracker.invalidate(sessionId, false);
+	}
+
 	private Map<String, Object> attributes;
 	private final String targetFilter;
 	private final ServiceRegistration<ServletContextHelper> defaultContextReg;
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
index c4e7c8a..e783b52 100644
--- 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
@@ -69,7 +69,7 @@
 			List<HttpSessionAttributeListener> httpSessionAttributeListeners =
 				eventListeners.get(HttpSessionAttributeListener.class);
 
-			if (!httpSessionListeners.isEmpty()) {
+			if (!httpSessionAttributeListeners.isEmpty()) {
 				Enumeration<String> enumeration =
 					httpSessionAdaptor.getAttributeNames();
 
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ProxyServlet.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ProxyServlet.java
index a4d64bb..7f5b3c8 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ProxyServlet.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ProxyServlet.java
@@ -53,6 +53,10 @@
 		this.httpServiceRuntimeImpl = httpServiceRuntimeImpl;
 	}
 
+	public void sessionDestroyed(String sessionId) {
+		httpServiceRuntimeImpl.sessionDestroyed(sessionId);
+	}
+
 	public void sessionIdChanged(String oldSessionId) {
 		httpServiceRuntimeImpl.fireSessionIdChanged(oldSessionId);
 	}