Bug 497435 - [http servlet] performance optimizations, CPU and memory

https://issues.liferay.com/browse/LPS-66801
https://issues.liferay.com/browse/LPS-66813
https://issues.liferay.com/browse/LPS-66827
https://issues.liferay.com/browse/LPS-66847
https://issues.liferay.com/browse/LPS-66881
https://issues.liferay.com/browse/LPS-66903
https://issues.liferay.com/browse/LPS-66904
https://issues.liferay.com/browse/LPS-66908
https://issues.liferay.com/browse/LPS-66911
https://issues.liferay.com/browse/LPS-66959

Change-Id: I0374bfa5d566c2f01d34af57a64fd982b92ba4b8
Signed-off-by: shuyangzhou <shuyang.zhou@liferay.com>
Signed-off-by: Matthew Tambara <matthew.tambara@liferay.com>
Signed-off-by: Raymond Auge <raymond.auge@liferay.com>
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
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 9af775c..2a15d8a 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
@@ -15,6 +15,8 @@
 import java.net.URISyntaxException;
 import java.security.AccessController;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ConcurrentSkipListSet;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Pattern;
@@ -142,7 +144,7 @@
 		this.trackingContext = trackingContextParam;
 		this.consumingContext = consumingContext;
 
-		this.string = getClass().getSimpleName() + '[' + serviceId + "][" + contextName + ", " + consumingContext.getBundle() + ']'; //$NON-NLS-1$
+		this.string = SIMPLE_NAME + '[' + serviceId + "][" + contextName + ", " + trackingContextParam.getBundle() + ']'; //$NON-NLS-1$
 
 		listenerServiceTracker = new ServiceTracker<EventListener, AtomicReference<ListenerRegistration>>(
 			trackingContext, httpServiceRuntime.getListenerFilter(),
@@ -1180,20 +1182,22 @@
 	}
 
 	private void flushActiveSessions() {
-		Collection<HttpSessionAdaptor> currentActiveSessions;
-		synchronized (activeSessions) {
-			currentActiveSessions = new ArrayList<HttpSessionAdaptor>(activeSessions.values());
-			activeSessions.clear();
-		}
-		for (HttpSessionAdaptor httpSessionAdaptor : currentActiveSessions) {
+		Collection<HttpSessionAdaptor> httpSessionAdaptors =
+			activeSessions.values();
+
+		Iterator<HttpSessionAdaptor> iterator = httpSessionAdaptors.iterator();
+
+		while (iterator.hasNext()) {
+			HttpSessionAdaptor httpSessionAdaptor = iterator.next();
+
 			httpSessionAdaptor.invalidate();
+
+			iterator.remove();
 		}
 	}
 
 	public void removeActiveSession(HttpSession session) {
-		synchronized (activeSessions) {
-			activeSessions.remove(session);
-		}
+		activeSessions.remove(session.getId());
 	}
 
 	public void fireSessionIdChanged(String oldSessionId) {
@@ -1208,11 +1212,7 @@
 			return;
 		}
 
-		Collection<HttpSessionAdaptor> currentActiveSessions;
-		synchronized (activeSessions) {
-			currentActiveSessions = new ArrayList<HttpSessionAdaptor>(activeSessions.values());
-		}
-		for (HttpSessionAdaptor httpSessionAdaptor : currentActiveSessions) {
+		for (HttpSessionAdaptor httpSessionAdaptor : activeSessions.values()) {
 			HttpSessionEvent httpSessionEvent = new HttpSessionEvent(httpSessionAdaptor);
 			for (javax.servlet.http.HttpSessionIdListener listener : listeners) {
 				listener.sessionIdChanged(httpSessionEvent, oldSessionId);
@@ -1222,22 +1222,35 @@
 
 	public HttpSessionAdaptor getSessionAdaptor(
 		HttpSession session, ServletContext servletContext) {
-		boolean created = false;
-		HttpSessionAdaptor sessionAdaptor;
-		synchronized (activeSessions) {
-			sessionAdaptor = activeSessions.get(session);
-			if (sessionAdaptor == null) {
-				created = true;
-				sessionAdaptor = HttpSessionAdaptor.createHttpSessionAdaptor(session, servletContext, this);
-				activeSessions.put(session, sessionAdaptor);
-			}
+
+		String sessionId = session.getId();
+
+		HttpSessionAdaptor httpSessionAdaptor = activeSessions.get(sessionId);
+
+		if (httpSessionAdaptor != null) {
+			return httpSessionAdaptor;
 		}
-		if (created) {
-			for (HttpSessionListener listener : eventListeners.get(HttpSessionListener.class)) {
-				listener.sessionCreated(new HttpSessionEvent(sessionAdaptor));
-			}
+
+		httpSessionAdaptor = HttpSessionAdaptor.createHttpSessionAdaptor(
+			session, servletContext, this);
+
+		HttpSessionAdaptor previousHttpSessionAdaptor =
+			activeSessions.putIfAbsent(sessionId, httpSessionAdaptor);
+
+		if (previousHttpSessionAdaptor != null) {
+			return previousHttpSessionAdaptor;
 		}
-		return sessionAdaptor;
+
+		HttpSessionEvent httpSessionEvent = new HttpSessionEvent(
+			httpSessionAdaptor);
+
+		for (HttpSessionListener listener : eventListeners.get(
+				HttpSessionListener.class)) {
+
+			listener.sessionCreated(httpSessionEvent);
+		}
+
+		return httpSessionAdaptor;
 	}
 
 	private void validate(String preValidationContextName, String preValidationContextPath) {
@@ -1261,6 +1274,8 @@
 	private static final String[] DISPATCHER =
 		new String[] {DispatcherType.REQUEST.toString()};
 
+	private static final String SIMPLE_NAME = ContextController.class.getSimpleName();
+
 	private static final Pattern contextNamePattern = Pattern.compile("^([a-zA-Z_0-9\\-]+\\.)*[a-zA-Z_0-9\\-]+$"); //$NON-NLS-1$
 
 	private final Map<String, String> initParams;
@@ -1272,7 +1287,7 @@
 	private final Set<EndpointRegistration<?>> endpointRegistrations = new ConcurrentSkipListSet<EndpointRegistration<?>>();
 	private final EventListeners eventListeners = new EventListeners();
 	private final Set<FilterRegistration> filterRegistrations = new ConcurrentSkipListSet<FilterRegistration>();
-	private final Map<HttpSession, HttpSessionAdaptor> activeSessions = new HashMap<HttpSession, HttpSessionAdaptor>();
+	private final ConcurrentMap<String, HttpSessionAdaptor> activeSessions = new ConcurrentHashMap<String, HttpSessionAdaptor>();
 
 	private final HttpServiceRuntimeImpl httpServiceRuntime;
 	private final Set<ListenerRegistration> listenerRegistrations = new HashSet<ListenerRegistration>();
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/DispatchTargets.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/DispatchTargets.java
index 903166f..6e30ca0 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/DispatchTargets.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/DispatchTargets.java
@@ -54,8 +54,6 @@
 		this.servletPath = (servletPath == null) ? Const.BLANK : servletPath;
 		this.pathInfo = pathInfo;
 		this.queryString = queryString;
-
-		this.string = SIMPLE_NAME + '[' + contextController.getFullContextPath() + requestURI + (queryString != null ? '?' + queryString : "") + ", " + endpointRegistration.toString() + ']'; //$NON-NLS-1$ //$NON-NLS-2$
 	}
 
 	public void addRequestParameters(HttpServletRequest request) {
@@ -197,7 +195,15 @@
 
 	@Override
 	public String toString() {
-		return string;
+		String value = string;
+
+		if (value == null) {
+			value = SIMPLE_NAME + '[' + contextController.getFullContextPath() + requestURI + (queryString != null ? '?' + queryString : "") + ", " + endpointRegistration.toString() + ']'; //$NON-NLS-1$
+
+			string = value;
+		}
+
+		return value;
 	}
 
 	private static Map<String, String[]> queryStringToParameterMap(String queryString) {
@@ -266,6 +272,6 @@
 	private final String servletPath;
 	private final String servletName;
 	private final Map<String, Object> specialOverides = new ConcurrentHashMap<String, Object>();
-	private final String string;
+	private String string;
 
 }
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/EndpointRegistration.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/EndpointRegistration.java
index 067a04f..2c8e87d 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/EndpointRegistration.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/EndpointRegistration.java
@@ -168,7 +168,19 @@
 
 	@Override
 	public String toString() {
-		return getClass().getSimpleName() + '[' + getD().toString() + ']';
+		String toString = _toString;
+
+		if (toString == null) {
+			toString = SIMPLE_NAME + '[' + getD().toString() + ']';
+
+			_toString = toString;
+		}
+
+		return toString;
 	}
 
+	private static final String SIMPLE_NAME =
+		EndpointRegistration.class.getSimpleName();
+
+	private String _toString;
 }
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/Registration.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/Registration.java
index 0df5d73..4e3e612 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/Registration.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/registration/Registration.java
@@ -12,6 +12,11 @@
  *******************************************************************************/
 package org.eclipse.equinox.http.servlet.internal.registration;
 
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import org.osgi.dto.DTO;
 
 public abstract class Registration<T, D extends DTO> {
@@ -19,42 +24,82 @@
 	private final D d;
 	private final T t;
 
-	protected int referenceCount;
+	protected final AtomicInteger referenceCount = new AtomicInteger();
 
 	public Registration(T t, D d) {
 		this.t = t;
 		this.d = d;
 	}
 
-	public synchronized void addReference() {
-		++referenceCount;
-	}
+	public void addReference() {
+		readLock.lock();
 
-	public synchronized void removeReference() {
-		--referenceCount;
-		if (referenceCount == 0) {
-			notifyAll();
+		try {
+			referenceCount.incrementAndGet();
+		}
+		finally {
+			readLock.unlock();
 		}
 	}
 
-	public synchronized void destroy() {
-		boolean interrupted = false;
+	public void removeReference() {
+		readLock.lock();
+
 		try {
-			while (referenceCount != 0) {
+			if (referenceCount.decrementAndGet() == 0 && destroyed) {
+				readLock.unlock();
+
+				writeLock.lock();
+
 				try {
-					(new Exception()).printStackTrace();
-					wait();
-				} catch (InterruptedException e) {
-					// wait until the servlet is inactive but save the interrupted status
+					condition.signalAll();
+				}
+				finally {
+					writeLock.unlock();
+
+					readLock.lock();
+				}
+			}
+		}
+		finally {
+			readLock.unlock();
+		}
+	}
+
+	public void destroy() {
+		boolean interrupted = false;
+
+		writeLock.lock();
+
+		destroyed = true;
+
+		try {
+			while (referenceCount.get() != 0) {
+				try {
+					condition.await();
+				}
+				catch (InterruptedException ie) {
 					interrupted = true;
 				}
 			}
-		} finally {
-			if (interrupted)
-				Thread.currentThread().interrupt(); //restore the interrupted state
+		}
+		finally {
+			writeLock.unlock();
+
+			if (interrupted) {
+				Thread.currentThread().interrupt();
+			}
 		}
 	}
 
+	private volatile boolean destroyed;
+
+	private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+
+	private final Lock readLock = readWriteLock.readLock();
+	private final Lock writeLock = readWriteLock.writeLock();
+	private final Condition condition = writeLock.newCondition();
+
 	public D getD() {
 		return d;
 	}
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 30f03be..6472681 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
@@ -32,24 +32,26 @@
 	private Map<String, Part> parts;
 	private final Lock lock = new ReentrantLock();
 
-	private static final String[] dispatcherAttributes = new String[] {
-		RequestDispatcher.ERROR_EXCEPTION,
-		RequestDispatcher.ERROR_EXCEPTION_TYPE,
-		RequestDispatcher.ERROR_MESSAGE,
-		RequestDispatcher.ERROR_REQUEST_URI,
-		RequestDispatcher.ERROR_SERVLET_NAME,
-		RequestDispatcher.ERROR_STATUS_CODE,
-		RequestDispatcher.FORWARD_CONTEXT_PATH,
-		RequestDispatcher.FORWARD_PATH_INFO,
-		RequestDispatcher.FORWARD_QUERY_STRING,
-		RequestDispatcher.FORWARD_REQUEST_URI,
-		RequestDispatcher.FORWARD_SERVLET_PATH,
-		RequestDispatcher.INCLUDE_CONTEXT_PATH,
-		RequestDispatcher.INCLUDE_PATH_INFO,
-		RequestDispatcher.INCLUDE_QUERY_STRING,
-		RequestDispatcher.INCLUDE_REQUEST_URI,
-		RequestDispatcher.INCLUDE_SERVLET_PATH
-	};
+	private static final Set<String> dispatcherAttributes =	new HashSet<String>();
+
+	static {
+		dispatcherAttributes.add(RequestDispatcher.ERROR_EXCEPTION);
+		dispatcherAttributes.add(RequestDispatcher.ERROR_EXCEPTION_TYPE);
+		dispatcherAttributes.add(RequestDispatcher.ERROR_MESSAGE);
+		dispatcherAttributes.add(RequestDispatcher.ERROR_REQUEST_URI);
+		dispatcherAttributes.add(RequestDispatcher.ERROR_SERVLET_NAME);
+		dispatcherAttributes.add(RequestDispatcher.ERROR_STATUS_CODE);
+		dispatcherAttributes.add(RequestDispatcher.FORWARD_CONTEXT_PATH);
+		dispatcherAttributes.add(RequestDispatcher.FORWARD_PATH_INFO);
+		dispatcherAttributes.add(RequestDispatcher.FORWARD_QUERY_STRING);
+		dispatcherAttributes.add(RequestDispatcher.FORWARD_REQUEST_URI);
+		dispatcherAttributes.add(RequestDispatcher.FORWARD_SERVLET_PATH);
+		dispatcherAttributes.add(RequestDispatcher.INCLUDE_CONTEXT_PATH);
+		dispatcherAttributes.add(RequestDispatcher.INCLUDE_PATH_INFO);
+		dispatcherAttributes.add(RequestDispatcher.INCLUDE_QUERY_STRING);
+		dispatcherAttributes.add(RequestDispatcher.INCLUDE_REQUEST_URI);
+		dispatcherAttributes.add(RequestDispatcher.INCLUDE_SERVLET_PATH);
+	}
 
 	public static HttpServletRequestWrapperImpl findHttpRuntimeRequest(
 		HttpServletRequest request) {
@@ -87,11 +89,13 @@
 	}
 
 	public String getPathInfo() {
-		if ((dispatchTargets.peek().getServletName() != null) ||
-			(dispatchTargets.peek().getDispatcherType() == DispatcherType.INCLUDE)) {
+		DispatchTargets currentDispatchTargets = dispatchTargets.peek();
+
+		if ((currentDispatchTargets.getServletName() != null) ||
+			(currentDispatchTargets.getDispatcherType() == DispatcherType.INCLUDE)) {
 			return this.dispatchTargets.get(0).getPathInfo();
 		}
-		return this.dispatchTargets.peek().getPathInfo();
+		return currentDispatchTargets.getPathInfo();
 	}
 
 	public DispatcherType getDispatcherType() {
@@ -120,20 +124,24 @@
 
 	@Override
 	public String getQueryString() {
-		if ((dispatchTargets.peek().getServletName() != null) ||
-			(dispatchTargets.peek().getDispatcherType() == DispatcherType.INCLUDE)) {
+		DispatchTargets currentDispatchTargets = dispatchTargets.peek();
+
+		if ((currentDispatchTargets.getServletName() != null) ||
+			(currentDispatchTargets.getDispatcherType() == DispatcherType.INCLUDE)) {
 			return request.getQueryString();
 		}
-		return this.dispatchTargets.peek().getQueryString();
+		return currentDispatchTargets.getQueryString();
 	}
 
 	@Override
 	public String getRequestURI() {
-		if ((dispatchTargets.peek().getServletName() != null) ||
-			(dispatchTargets.peek().getDispatcherType() == DispatcherType.INCLUDE)) {
+		DispatchTargets currentDispatchTargets = dispatchTargets.peek();
+
+		if ((currentDispatchTargets.getServletName() != null) ||
+			(currentDispatchTargets.getDispatcherType() == DispatcherType.INCLUDE)) {
 			return request.getRequestURI();
 		}
-		return this.dispatchTargets.peek().getRequestURI();
+		return currentDispatchTargets.getRequestURI();
 	}
 
 	public ServletContext getServletContext() {
@@ -141,14 +149,16 @@
 	}
 
 	public String getServletPath() {
-		if ((dispatchTargets.peek().getServletName() != null) ||
-			(dispatchTargets.peek().getDispatcherType() == DispatcherType.INCLUDE)) {
+		DispatchTargets currentDispatchTargets = dispatchTargets.peek();
+
+		if ((currentDispatchTargets.getServletName() != null) ||
+			(currentDispatchTargets.getDispatcherType() == DispatcherType.INCLUDE)) {
 			return this.dispatchTargets.get(0).getServletPath();
 		}
-		if (dispatchTargets.peek().getServletPath().equals(Const.SLASH)) {
+		if (currentDispatchTargets.getServletPath().equals(Const.SLASH)) {
 			return Const.BLANK;
 		}
-		return this.dispatchTargets.peek().getServletPath();
+		return currentDispatchTargets.getServletPath();
 	}
 
 	public String getContextPath() {
@@ -157,19 +167,20 @@
 
 	public Object getAttribute(String attributeName) {
 		DispatchTargets current = dispatchTargets.peek();
-
+		DispatcherType dispatcherType = current.getDispatcherType();
+		boolean hasServletName = (current.getServletName() != null);
 		Map<String, Object> specialOverides = current.getSpecialOverides();
 
-		if (current.getDispatcherType() == DispatcherType.ERROR) {
-			if ((Arrays.binarySearch(dispatcherAttributes, attributeName) > -1) &&
+		if (dispatcherType == DispatcherType.ERROR) {
+			if (dispatcherAttributes.contains(attributeName) &&
 				!attributeName.startsWith("javax.servlet.error.")) { //$NON-NLS-1$
 
 				return null;
 			}
 		}
-		else if (current.getDispatcherType() == DispatcherType.INCLUDE) {
+		else if (dispatcherType == DispatcherType.INCLUDE) {
 			if (attributeName.equals(RequestDispatcher.INCLUDE_CONTEXT_PATH)) {
-				if (current.getServletName() != null) {
+				if (hasServletName) {
 					return null;
 				}
 				if (specialOverides.containsKey(RequestDispatcher.INCLUDE_CONTEXT_PATH)) {
@@ -178,7 +189,7 @@
 				return current.getContextController().getContextPath();
 			}
 			else if (attributeName.equals(RequestDispatcher.INCLUDE_PATH_INFO)) {
-				if (current.getServletName() != null) {
+				if (hasServletName) {
 					return null;
 				}
 				if (specialOverides.containsKey(RequestDispatcher.INCLUDE_PATH_INFO)) {
@@ -187,7 +198,7 @@
 				return current.getPathInfo();
 			}
 			else if (attributeName.equals(RequestDispatcher.INCLUDE_QUERY_STRING)) {
-				if (current.getServletName() != null) {
+				if (hasServletName) {
 					return null;
 				}
 				if (specialOverides.containsKey(RequestDispatcher.INCLUDE_QUERY_STRING)) {
@@ -196,7 +207,7 @@
 				return current.getQueryString();
 			}
 			else if (attributeName.equals(RequestDispatcher.INCLUDE_REQUEST_URI)) {
-				if (current.getServletName() != null) {
+				if (hasServletName) {
 					return null;
 				}
 				if (specialOverides.containsKey(RequestDispatcher.INCLUDE_REQUEST_URI)) {
@@ -205,7 +216,7 @@
 				return current.getRequestURI();
 			}
 			else if (attributeName.equals(RequestDispatcher.INCLUDE_SERVLET_PATH)) {
-				if (current.getServletName() != null) {
+				if (hasServletName) {
 					return null;
 				}
 				if (specialOverides.containsKey(RequestDispatcher.INCLUDE_SERVLET_PATH)) {
@@ -214,60 +225,49 @@
 				return current.getServletPath();
 			}
 
-			if (Arrays.binarySearch(dispatcherAttributes, attributeName) > -1) {
+			if (dispatcherAttributes.contains(attributeName)) {
 				return null;
 			}
 		}
-		else if (current.getDispatcherType() == DispatcherType.FORWARD) {
+		else if (dispatcherType == DispatcherType.FORWARD) {
+			if (hasServletName && attributeName.startsWith("javax.servlet.forward")) {
+				return null;
+			}
+
 			DispatchTargets original = dispatchTargets.get(0);
 
 			if (attributeName.equals(RequestDispatcher.FORWARD_CONTEXT_PATH)) {
-				if (current.getServletName() != null) {
-					return null;
-				}
 				if (specialOverides.containsKey(RequestDispatcher.FORWARD_CONTEXT_PATH)) {
 					return specialOverides.get(RequestDispatcher.FORWARD_CONTEXT_PATH);
 				}
 				return original.getContextController().getContextPath();
 			}
 			else if (attributeName.equals(RequestDispatcher.FORWARD_PATH_INFO)) {
-				if (current.getServletName() != null) {
-					return null;
-				}
 				if (specialOverides.containsKey(RequestDispatcher.FORWARD_PATH_INFO)) {
 					return specialOverides.get(RequestDispatcher.FORWARD_PATH_INFO);
 				}
 				return original.getPathInfo();
 			}
 			else if (attributeName.equals(RequestDispatcher.FORWARD_QUERY_STRING)) {
-				if (current.getServletName() != null) {
-					return null;
-				}
 				if (specialOverides.containsKey(RequestDispatcher.FORWARD_QUERY_STRING)) {
 					return specialOverides.get(RequestDispatcher.FORWARD_QUERY_STRING);
 				}
 				return original.getQueryString();
 			}
 			else if (attributeName.equals(RequestDispatcher.FORWARD_REQUEST_URI)) {
-				if (current.getServletName() != null) {
-					return null;
-				}
 				if (specialOverides.containsKey(RequestDispatcher.FORWARD_REQUEST_URI)) {
 					return specialOverides.get(RequestDispatcher.FORWARD_REQUEST_URI);
 				}
 				return original.getRequestURI();
 			}
 			else if (attributeName.equals(RequestDispatcher.FORWARD_SERVLET_PATH)) {
-				if (current.getServletName() != null) {
-					return null;
-				}
 				if (specialOverides.containsKey(RequestDispatcher.FORWARD_SERVLET_PATH)) {
 					return specialOverides.get(RequestDispatcher.FORWARD_SERVLET_PATH);
 				}
 				return original.getServletPath();
 			}
 
-			if (Arrays.binarySearch(dispatcherAttributes, attributeName) > -1) {
+			if (dispatcherAttributes.contains(attributeName)) {
 				return null;
 			}
 		}
@@ -276,12 +276,14 @@
 	}
 
 	public RequestDispatcher getRequestDispatcher(String path) {
+		DispatchTargets currentDispatchTarget = dispatchTargets.peek();
+
 		ContextController contextController =
-			this.dispatchTargets.peek().getContextController();
+			currentDispatchTarget.getContextController();
 
 		// support relative paths
 		if (!path.startsWith(Const.SLASH)) {
-			path = this.dispatchTargets.peek().getServletPath() + Const.SLASH + path;
+			path = currentDispatchTarget.getServletPath() + Const.SLASH + path;
 		}
 		// if the path starts with the full context path strip it
 		else if (path.startsWith(contextController.getFullContextPath())) {
@@ -305,20 +307,16 @@
 	}
 
 	public HttpSession getSession() {
-		HttpSession session = request.getSession();
-		if (session != null) {
-			return dispatchTargets.peek().getContextController().getSessionAdaptor(
-				session, dispatchTargets.peek().getServletRegistration().getT().getServletConfig().getServletContext());
-		}
-
-		return null;
+		return getSession(true);
 	}
 
 	public HttpSession getSession(boolean create) {
 		HttpSession session = request.getSession(create);
 		if (session != null) {
-			return dispatchTargets.peek().getContextController().getSessionAdaptor(
-				session, dispatchTargets.peek().getServletRegistration().getT().getServletConfig().getServletContext());
+			DispatchTargets currentDispatchTarget = dispatchTargets.peek();
+
+			return currentDispatchTarget.getContextController().getSessionAdaptor(
+				session, currentDispatchTarget.getServletRegistration().getT().getServletConfig().getServletContext());
 		}
 
 		return null;
@@ -336,7 +334,7 @@
 	}
 
 	public void removeAttribute(String name) {
-		if (Arrays.binarySearch(dispatcherAttributes, name) > -1) {
+		if (dispatcherAttributes.contains(name)) {
 			DispatchTargets current = dispatchTargets.peek();
 
 			current.getSpecialOverides().remove(name);
@@ -345,7 +343,9 @@
 			request.removeAttribute(name);
 		}
 
-		EventListeners eventListeners = dispatchTargets.peek().getContextController().getEventListeners();
+		DispatchTargets currentDispatchTarget = dispatchTargets.peek();
+
+		EventListeners eventListeners = currentDispatchTarget.getContextController().getEventListeners();
 
 		List<ServletRequestAttributeListener> listeners = eventListeners.get(
 			ServletRequestAttributeListener.class);
@@ -356,7 +356,7 @@
 
 		ServletRequestAttributeEvent servletRequestAttributeEvent =
 			new ServletRequestAttributeEvent(
-				dispatchTargets.peek().getServletRegistration().getServletContext(), this, name, null);
+				currentDispatchTarget.getServletRegistration().getServletContext(), this, name, null);
 
 		for (ServletRequestAttributeListener servletRequestAttributeListener : listeners) {
 			servletRequestAttributeListener.attributeRemoved(
@@ -367,7 +367,7 @@
 	public void setAttribute(String name, Object value) {
 		boolean added = (request.getAttribute(name) == null);
 
-		if (Arrays.binarySearch(dispatcherAttributes, name) > -1) {
+		if (dispatcherAttributes.contains(name)) {
 			DispatchTargets current = dispatchTargets.peek();
 
 			if (value == null) {
@@ -381,7 +381,9 @@
 			request.setAttribute(name, value);
 		}
 
-		EventListeners eventListeners = dispatchTargets.peek().getContextController().getEventListeners();
+		DispatchTargets currentDispatchTarget = dispatchTargets.peek();
+
+		EventListeners eventListeners = currentDispatchTarget.getContextController().getEventListeners();
 
 		List<ServletRequestAttributeListener> listeners = eventListeners.get(
 			ServletRequestAttributeListener.class);
@@ -392,7 +394,7 @@
 
 		ServletRequestAttributeEvent servletRequestAttributeEvent =
 			new ServletRequestAttributeEvent(
-				dispatchTargets.peek().getServletRegistration().getServletContext(), this, name, value);
+				currentDispatchTarget.getServletRegistration().getServletContext(), this, name, value);
 
 		for (ServletRequestAttributeListener servletRequestAttributeListener : listeners) {
 			if (added) {
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 14212dd..58ca6e5 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,6 +14,7 @@
 
 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;
@@ -27,7 +28,7 @@
 		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 = new HashSet<HttpSessionAdaptor>();
+		transient final Set<HttpSessionAdaptor> innerSessions = Collections.newSetFromMap(new ConcurrentHashMap<HttpSessionAdaptor, Boolean>());
 		@Override
 		public void valueBound(HttpSessionBindingEvent event) {
 			// do nothing
@@ -37,42 +38,64 @@
 		public void valueUnbound(HttpSessionBindingEvent event) {
 			// Here we assume the unbound event is signifying the session is being invalidated.
 			// Must invalidate the inner sessions
-			Set<HttpSessionAdaptor> innerSessionsToInvalidate;
-			synchronized (innerSessions) {
-				// copy the sessions to invalidate and clear the set
-				innerSessionsToInvalidate = new HashSet<HttpSessionAdaptor>(innerSessions);
-				innerSessions.clear();
-			}
-			for (HttpSessionAdaptor innerSession : innerSessionsToInvalidate) {
-				innerSession.invalidate();
+			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 (ParentSessionListener.class) {
-				parentListener = (ParentSessionListener) innerSession.getSession().getAttribute(PARENT_SESSION_LISTENER_KEY);
+			synchronized (httpSession) {
+				parentListener = (ParentSessionListener) httpSession.getAttribute(PARENT_SESSION_LISTENER_KEY);
 				if (parentListener == null) {
 					parentListener = new ParentSessionListener();
-					innerSession.getSession().setAttribute(PARENT_SESSION_LISTENER_KEY, parentListener);
+					httpSession.setAttribute(PARENT_SESSION_LISTENER_KEY, parentListener);
 				}
 			}
-			synchronized (parentListener.innerSessions) {
-				parentListener.innerSessions.add(innerSession);
-			}
+
+			parentListener.innerSessions.add(innerSession);
 		}
 
 		static void removeHttpSessionAdaptor(HttpSessionAdaptor innerSession) {
-			ParentSessionListener parentListener;
-			// need to have a global lock here because we must ensure that this is added only once
-			synchronized (ParentSessionListener.class) {
-				parentListener = (ParentSessionListener) innerSession.getSession().getAttribute(PARENT_SESSION_LISTENER_KEY);
-			}
+			HttpSession httpSession = innerSession.getSession();
+
+			ParentSessionListener parentListener = (ParentSessionListener) httpSession.getAttribute(PARENT_SESSION_LISTENER_KEY);
+
 			if (parentListener != null) {
-				synchronized (parentListener.innerSessions) {
-					parentListener.innerSessions.remove(innerSession);
-				}
+				parentListener.innerSessions.remove(innerSession);
 			}
 		}
 	}
@@ -172,7 +195,7 @@
 		this.controller = controller;
 		this.attributePrefix = "equinox.http." + controller.getContextName(); //$NON-NLS-1$
 
-		this.string = getClass().getSimpleName() + '[' + session.getId() + ", " + attributePrefix + ']'; //$NON-NLS-1$
+		this.string = SIMPLE_NAME + '[' + session.getId() + ", " + attributePrefix + ']'; //$NON-NLS-1$
 	}
 
 	public ContextController getController() {
@@ -317,4 +340,8 @@
 	public String toString() {
 		return string;
 	}
+
+	private static final String SIMPLE_NAME =
+		HttpSessionAdaptor.class.getSimpleName();
+
 }
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/RequestDispatcherAdaptor.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/RequestDispatcherAdaptor.java
index c3791ad..c07d818 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/RequestDispatcherAdaptor.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/RequestDispatcherAdaptor.java
@@ -21,6 +21,8 @@
 //This class unwraps the request so it can be processed by the underlying servlet container.
 public class RequestDispatcherAdaptor implements RequestDispatcher {
 
+	private static final String SIMPLE_NAME = RequestDispatcherAdaptor.class.getSimpleName();
+
 	private final DispatchTargets dispatchTargets;
 	private final String path;
 	private final String string;
@@ -31,7 +33,7 @@
 		this.dispatchTargets = dispatchTargets;
 		this.path = path;
 
-		this.string = getClass().getSimpleName() + '[' + path + ", " + dispatchTargets + ']'; //$NON-NLS-1$
+		this.string = SIMPLE_NAME + '[' + path + ", " + dispatchTargets + ']'; //$NON-NLS-1$
 	}
 
 	public void forward(ServletRequest request, ServletResponse response)
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ServletContextAdaptor.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ServletContextAdaptor.java
index 9eb6566..35a87d4 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ServletContextAdaptor.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/servlet/ServletContextAdaptor.java
@@ -88,7 +88,7 @@
 
 		this.classLoader = bundleWiring.getClassLoader();
 
-		this.string = getClass().getSimpleName() + '[' + contextController + ']';
+		this.string = SIMPLE_NAME + '[' + contextController + ']';
 	}
 
 	public ServletContext createServletContext() {
@@ -385,7 +385,13 @@
 	}
 
 	Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-		servletContextTL.set((ServletContext)proxy);
+		boolean useThreadLocal =
+			"removeAttribute".equals(method.getName()) ||
+			"setAttribute".equals(method.getName());
+
+		if (useThreadLocal) {
+			servletContextTL.set((ServletContext)proxy);
+		}
 
 		try {
 			Method m = contextToHandlerMethods.get(method);
@@ -399,7 +405,9 @@
 			}
 		}
 		finally {
-			servletContextTL.remove();
+			if (useThreadLocal) {
+				servletContextTL.remove();
+			}
 		}
 	}
 
@@ -422,6 +430,11 @@
 
 	}
 
+	private final static String SIMPLE_NAME =
+		ServletContextAdaptor.class.getSimpleName();
+
+	private final static ThreadLocal<ServletContext> servletContextTL = new ThreadLocal<ServletContext>();
+
 	private final AccessControlContext acc;
 	private final Bundle bundle;
 	private final ClassLoader classLoader;
@@ -430,7 +443,6 @@
 	private final ProxyContext proxyContext;
 	private final ServletContext servletContext;
 	final ServletContextHelper servletContextHelper;
-	private final ThreadLocal<ServletContext> servletContextTL = new ThreadLocal<ServletContext>();
 	private final String string;
 
 }