org.apache.catalina.valves.CometConnectionManagerValve Maven / Gradle / Ivy
/*
* 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;
/**
* Implementation of a Valve that tracks Comet connections, and closes them
* when the associated session expires or the webapp is reloaded.
*
* This Valve should be attached to a Context.
*
* @author Remy Maucherat
* @version $Revision: 769328 $ $Date: 2009-04-28 18:39:24 +0800 (Tue, 28 Apr 2009) $
*/
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 cometRequests =
Collections.synchronizedList(new ArrayList());
/**
* 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 configure()
,
* 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 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);
}
}
}
}
}