blob: 17367461ef4515a61e4174d31995f87e0b73bc6e [file] [log] [blame]
/*
* 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 static 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;
}
}
}