--- /dev/null
+/*
+ * Copyright 1999-2001,2005-2006 The Apache Software Foundation.
+ *
+ * Licensed 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.catalina.valves;
+
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.catalina.CometEvent;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.connector.CometEventImpl;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.util.LifecycleSupport;
+import org.apache.catalina.util.StringManager;
+
+
+/**
+ * <p>Implementation of a Valve that tracks Comet connections, and closes them
+ * when the associated session expires or the webapp is reloaded.</p>
+ *
+ * <p>This Valve may be attached to any Container, depending on the granularity
+ * of the concurrency control you wish to perform.</p>
+ *
+ * @author Remy Maucherat
+ * @version $Revision: 386404 $ $Date: 2006-03-16 18:50:37 +0100 (jeu., 16 mars 2006) $
+ */
+
+public class CometConnectionManagerValve
+ extends ValveBase
+ implements Lifecycle, HttpSessionListener {
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * The descriptive information related to this implementation.
+ */
+ protected static final String info =
+ "org.apache.catalina.valves.CometConnectionManagerValve/1.0";
+
+
+ /**
+ * The string manager for this package.
+ */
+ protected StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+
+ /**
+ * The lifecycle event support for this component.
+ */
+ protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+
+ /**
+ * Has this component been started yet?
+ */
+ protected boolean started = false;
+
+
+ /**
+ * Connection list.
+ */
+ protected ConcurrentHashMap<String, ConnectionInfo[]> connections
+ = new ConcurrentHashMap<String, ConnectionInfo[]>();
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ // ------------------------------------------------------ Lifecycle Methods
+
+
+ /**
+ * Add a lifecycle event listener to this component.
+ *
+ * @param listener The listener to add
+ */
+ public void addLifecycleListener(LifecycleListener listener) {
+
+ lifecycle.addLifecycleListener(listener);
+
+ }
+
+
+ /**
+ * Get the lifecycle listeners associated with this lifecycle. If this
+ * Lifecycle has no listeners registered, a zero-length array is returned.
+ */
+ public LifecycleListener[] findLifecycleListeners() {
+
+ return lifecycle.findLifecycleListeners();
+
+ }
+
+
+ /**
+ * Remove a lifecycle event listener from this component.
+ *
+ * @param listener The listener to add
+ */
+ public void removeLifecycleListener(LifecycleListener listener) {
+
+ lifecycle.removeLifecycleListener(listener);
+
+ }
+
+
+ /**
+ * Prepare for the beginning of active use of the public methods of this
+ * component. This method should be called after <code>configure()</code>,
+ * and before any of the public methods of the component are utilized.
+ *
+ * @exception LifecycleException if this component detects a fatal error
+ * that prevents this component from being used
+ */
+ public void start() throws LifecycleException {
+
+ // Validate and update our current component state
+ if (started)
+ throw new LifecycleException
+ (sm.getString("semaphoreValve.alreadyStarted"));
+ lifecycle.fireLifecycleEvent(START_EVENT, null);
+ started = true;
+
+ }
+
+
+ /**
+ * Gracefully terminate the active use of the public methods of this
+ * component. This method should be the last one called on a given
+ * instance of this component.
+ *
+ * @exception LifecycleException if this component detects a fatal error
+ * that needs to be reported
+ */
+ public void stop() throws LifecycleException {
+
+ // Validate and update our current component state
+ if (!started)
+ throw new LifecycleException
+ (sm.getString("semaphoreValve.notStarted"));
+ lifecycle.fireLifecycleEvent(STOP_EVENT, null);
+ started = false;
+
+ // The webapp is getting stopped, so all current connections
+ // should be closed
+ // Close all Comet connections associated with this session
+ Iterator<ConnectionInfo[]> iterator = connections.values().iterator();
+ while (iterator.hasNext()) {
+ ConnectionInfo[] connectionInfos = iterator.next();
+ if (connectionInfos != null) {
+ for (int i = 0; i < connectionInfos.length; i++) {
+ ConnectionInfo connectionInfo = connectionInfos[i];
+ try {
+ ((CometEventImpl) connectionInfo.event).setEventType(CometEvent.EventType.END);
+ ((CometEventImpl) connectionInfo.event).setEventSubType(CometEvent.EventSubType.WEBAPP_RELOAD);
+ getNext().event(connectionInfo.request, connectionInfo.response, connectionInfo.event);
+ connectionInfo.event.close();
+ } catch (Exception e) {
+ container.getLogger().warn(sm.getString("cometConnectionManagerValve.event"), e);
+ }
+ }
+ }
+ }
+
+ }
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Return descriptive information about this Valve implementation.
+ */
+ public String getInfo() {
+ return (info);
+ }
+
+
+ /**
+ * Register requests for tracking, whenever needed.
+ *
+ * @param request The servlet request to be processed
+ * @param response The servlet response to be created
+ *
+ * @exception IOException if an input/output error occurs
+ * @exception ServletException if a servlet error occurs
+ */
+ public void invoke(Request request, Response response)
+ throws IOException, ServletException {
+ // Perform the request
+ getNext().invoke(request, response);
+
+ if (request.isComet() && !response.isClosed()) {
+ // Start tracking this connection, since this is a
+ // begin event, and Comet mode is on
+ HttpSession session = request.getSession(true);
+ ConnectionInfo newConnectionInfo = new ConnectionInfo();
+ newConnectionInfo.request = request;
+ newConnectionInfo.response = response;
+ newConnectionInfo.event = request.getEvent();
+ synchronized (session) {
+ String id = session.getId();
+ ConnectionInfo[] connectionInfos = connections.get(id);
+ if (connectionInfos == null) {
+ connectionInfos = new ConnectionInfo[1];
+ connectionInfos[0] = newConnectionInfo;
+ connections.put(id, connectionInfos);
+ } else {
+ ConnectionInfo[] newConnectionInfos =
+ new ConnectionInfo[connectionInfos.length + 1];
+ for (int i = 0; i < connectionInfos.length; i++) {
+ newConnectionInfos[i] = connectionInfos[i];
+ }
+ newConnectionInfos[connectionInfos.length] = newConnectionInfo;
+ connections.put(id, newConnectionInfos);
+ }
+ }
+ }
+
+ }
+
+
+ /**
+ * Use events to update the connection state.
+ *
+ * @param request The servlet request to be processed
+ * @param response The servlet response to be created
+ *
+ * @exception IOException if an input/output error occurs
+ * @exception ServletException if a servlet error occurs
+ */
+ public void event(Request request, Response response, CometEvent event)
+ throws IOException, ServletException {
+
+ // Perform the request
+ boolean ok = false;
+ try {
+ getNext().event(request, response, event);
+ ok = true;
+ } finally {
+ if (!ok || response.isClosed()
+ || (event.getEventType() == CometEvent.EventType.END)
+ || (event.getEventType() == CometEvent.EventType.ERROR
+ && !(event.getEventSubType() == CometEvent.EventSubType.TIMEOUT))) {
+ // Remove from tracked list, the connection is done
+ HttpSession session = request.getSession(true);
+ synchronized (session) {
+ ConnectionInfo[] connectionInfos = connections.get(session.getId());
+ if (connectionInfos != null) {
+ boolean found = false;
+ for (int i = 0; !found && (i < connectionInfos.length); i++) {
+ found = (connectionInfos[i].request == request);
+ }
+ if (found) {
+ ConnectionInfo[] newConnectionInfos =
+ new ConnectionInfo[connectionInfos.length - 1];
+ int pos = 0;
+ for (int i = 0; i < connectionInfos.length; i++) {
+ if (connectionInfos[i].request != request) {
+ newConnectionInfos[pos++] = connectionInfos[i];
+ }
+ }
+ connections.put(session.getId(), newConnectionInfos);
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+
+ public void sessionCreated(HttpSessionEvent se) {
+ }
+
+
+ public void sessionDestroyed(HttpSessionEvent se) {
+ // Close all Comet connections associated with this session
+ ConnectionInfo[] connectionInfos = connections.remove(se.getSession().getId());
+ if (connectionInfos != null) {
+ for (int i = 0; i < connectionInfos.length; i++) {
+ ConnectionInfo connectionInfo = connectionInfos[i];
+ try {
+ ((CometEventImpl) connectionInfo.event).setEventType(CometEvent.EventType.END);
+ ((CometEventImpl) connectionInfo.event).setEventSubType(CometEvent.EventSubType.SESSION_END);
+ getNext().event(connectionInfo.request, connectionInfo.response, connectionInfo.event);
+ connectionInfo.event.close();
+ } catch (Exception e) {
+ container.getLogger().warn(sm.getString("cometConnectionManagerValve.event"), e);
+ }
+ }
+ }
+ }
+
+
+ // --------------------------------------------- ConnectionInfo Inner Class
+
+
+ protected class ConnectionInfo {
+ public CometEvent event;
+ public Request request;
+ public Response response;
+ }
+
+}