| /* |
| * 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.io.Serializable; |
| 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 |
| |
| /** |
| * 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 |
| |
| /** |
| * 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) { |
| ConnectionList list = (ConnectionList) session.getAttribute( |
| cometRequestsAttribute); |
| Request[] requests = null; |
| if (list != null) { |
| requests = list.get(); |
| } |
| if (requests == null) { |
| requests = new Request[1]; |
| requests[0] = request; |
| session.setAttribute(cometRequestsAttribute, |
| new ConnectionList(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, |
| new ConnectionList(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 { |
| ConnectionList list = |
| (ConnectionList) session.getAttribute( |
| cometRequestsAttribute); |
| if (list != null) { |
| reqs = list.get(); |
| } |
| } 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, |
| new ConnectionList( |
| 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 |
| ConnectionList list = (ConnectionList) se.getSession().getAttribute( |
| cometRequestsAttribute); |
| Request[] reqs = null; |
| if (list != null) { |
| reqs = list.get(); |
| } |
| 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); |
| } |
| } |
| } |
| } |
| |
| |
| private static class ConnectionList implements Serializable { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private transient Request[] connectionList = null; |
| |
| private ConnectionList(Request[] connectionList){ |
| this.connectionList = connectionList; |
| } |
| |
| public Request[] get(){ |
| return connectionList; |
| } |
| } |
| } |