/* * 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.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.LifecycleState; import org.apache.catalina.comet.CometEvent; import org.apache.catalina.comet.CometProcessor; import org.apache.catalina.connector.CometEventImpl; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; /** * <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 HttpSessionListener, LifecycleListener { //------------------------------------------------------ Constructor public CometConnectionManagerValve() { super(false); } // ----------------------------------------------------- Instance Variables /** * The descriptive information related to this implementation. */ protected static final String info = "org.apache.catalina.valves.CometConnectionManagerValve/1.0"; /** * List of current Comet connections. */ protected final List<Request> cometRequests = Collections.synchronizedList(new ArrayList<Request>()); /** * Name of session attribute used to store list of comet connections. */ protected final String cometRequestsAttribute = "org.apache.tomcat.comet.connectionList"; /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void startInternal() throws LifecycleException { if (container instanceof Context) { container.addLifecycleListener(this); } setState(LifecycleState.STARTING); } /** * Stop this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void stopInternal() throws LifecycleException { setState(LifecycleState.STOPPING); if (container instanceof Context) { container.removeLifecycleListener(this); } } @Override public void lifecycleEvent(LifecycleEvent event) { if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType())) { // 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 CometEventImpl cometEvent = request.getEvent(); try { cometEvent.setEventType(CometEvent.EventType.END); cometEvent.setEventSubType( CometEvent.EventSubType.WEBAPP_RELOAD); getNext().event(request, request.getResponse(), cometEvent); } catch (Exception e) { container.getLogger().warn( sm.getString("cometConnectionManagerValve.event"), e); } finally { try { cometEvent.close(); } catch (IOException e) { container.getLogger().warn(sm.getString( "cometConnectionManagerValve.event"), e); } } } cometRequests.clear(); } } // --------------------------------------------------------- Public Methods /** * Return descriptive information about this Valve implementation. */ @Override 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 */ @Override 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 connection 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 */ @Override 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 } } } } } } } } } @Override public void sessionCreated(HttpSessionEvent se) { // NOOP } @Override 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); } } } } }