/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.valves; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; 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.CometProcessor; import org.apache.catalina.Context; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; 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 should be attached to a Context.</p> * * @author Remy Maucherat * */ public class CometConnectionManagerValve extends ValveBase implements Lifecycle, HttpSessionListener, LifecycleListener { // ----------------------------------------------------- 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; /** * List of current Coment connections. */ protected List<Request> cometRequests = Collections.synchronizedList(new ArrayList<Request>()); /** * Name of session attribute used to store list of comet connections. */ protected String cometRequestsAttribute = "org.apache.tomcat.comet.connectionList"; // ------------------------------------------------------------- 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; if (container instanceof Context) { ((Lifecycle) container).addLifecycleListener(this); } } /** * 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; if (container instanceof Context) { ((Lifecycle) container).removeLifecycleListener(this); } } public void lifecycleEvent(LifecycleEvent event) { if (event.getType() == Lifecycle.BEFORE_STOP_EVENT) { // The container is getting stopped, close all current connections Iterator<Request> iterator = cometRequests.iterator(); while (iterator.hasNext()) { Request request = iterator.next(); // Remove the session tracking attribute as it isn't // serializable or required. HttpSession session = request.getSession(false); if (session != null) { session.removeAttribute(cometRequestsAttribute); } // Close the comet connection try { CometEventImpl cometEvent = request.getEvent(); cometEvent.setEventType(CometEvent.EventType.END); cometEvent.setEventSubType( CometEvent.EventSubType.WEBAPP_RELOAD); getNext().event(request, request.getResponse(), cometEvent); cometEvent.close(); } catch (Exception e) { container.getLogger().warn( sm.getString("cometConnectionManagerValve.event"), e); } } cometRequests.clear(); } } // --------------------------------------------------------- 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); // Track the conection for webapp reload cometRequests.add(request); // Track the connection for session expiration synchronized (session) { Request[] requests = (Request[]) session.getAttribute(cometRequestsAttribute); if (requests == null) { requests = new Request[1]; requests[0] = request; session.setAttribute(cometRequestsAttribute, requests); } else { Request[] newRequests = new Request[requests.length + 1]; for (int i = 0; i < requests.length; i++) { newRequests[i] = requests[i]; } newRequests[requests.length] = request; session.setAttribute(cometRequestsAttribute, newRequests); } } } } /** * 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 the connection from webapp reload tracking cometRequests.remove(request); // Remove connection from session expiration tracking // Note: can't get the session if it has been invalidated but // OK since session listener will have done clean-up HttpSession session = request.getSession(false); if (session != null) { synchronized (session) { Request[] reqs = null; try { reqs = (Request[]) session.getAttribute(cometRequestsAttribute); } catch (IllegalStateException ise) { // Ignore - session has been invalidated // Listener will have cleaned up } if (reqs != null) { boolean found = false; for (int i = 0; !found && (i < reqs.length); i++) { found = (reqs[i] == request); } if (found) { if (reqs.length > 1) { Request[] newConnectionInfos = new Request[reqs.length - 1]; int pos = 0; for (int i = 0; i < reqs.length; i++) { if (reqs[i] != request) { newConnectionInfos[pos++] = reqs[i]; } } try { session.setAttribute( cometRequestsAttribute, newConnectionInfos); } catch (IllegalStateException ise) { // Ignore - session has been invalidated // Listener will have cleaned up } } else { try { session.removeAttribute( cometRequestsAttribute); } catch (IllegalStateException ise) { // Ignore - session has been invalidated // Listener will have cleaned up } } } } } } } } } public void sessionCreated(HttpSessionEvent se) { } public void sessionDestroyed(HttpSessionEvent se) { // Close all Comet connections associated with this session Request[] reqs = (Request[]) se.getSession().getAttribute(cometRequestsAttribute); if (reqs != null) { for (int i = 0; i < reqs.length; i++) { Request req = reqs[i]; try { CometEventImpl event = req.getEvent(); event.setEventType(CometEvent.EventType.END); event.setEventSubType(CometEvent.EventSubType.SESSION_END); ((CometProcessor) req.getWrapper().getServlet()).event(event); event.close(); } catch (Exception e) { req.getWrapper().getParent().getLogger().warn(sm.getString( "cometConnectionManagerValve.listenerEvent"), e); } } } } }