001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.catalina.valves;
019:
020: import java.io.IOException;
021: import java.util.Iterator;
022: import java.util.concurrent.ConcurrentHashMap;
023:
024: import javax.servlet.ServletException;
025: import javax.servlet.http.HttpSession;
026: import javax.servlet.http.HttpSessionEvent;
027: import javax.servlet.http.HttpSessionListener;
028:
029: import org.apache.catalina.CometEvent;
030: import org.apache.catalina.Context;
031: import org.apache.catalina.Lifecycle;
032: import org.apache.catalina.LifecycleEvent;
033: import org.apache.catalina.LifecycleException;
034: import org.apache.catalina.LifecycleListener;
035: import org.apache.catalina.connector.CometEventImpl;
036: import org.apache.catalina.connector.Request;
037: import org.apache.catalina.connector.Response;
038: import org.apache.catalina.util.LifecycleSupport;
039: import org.apache.catalina.util.StringManager;
040:
041: /**
042: * <p>Implementation of a Valve that tracks Comet connections, and closes them
043: * when the associated session expires or the webapp is reloaded.</p>
044: *
045: * <p>This Valve should be attached to a Context.</p>
046: *
047: * @author Remy Maucherat
048: * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
049: */
050:
051: public class CometConnectionManagerValve extends ValveBase implements
052: Lifecycle, HttpSessionListener, LifecycleListener {
053:
054: // ----------------------------------------------------- Instance Variables
055:
056: /**
057: * The descriptive information related to this implementation.
058: */
059: protected static final String info = "org.apache.catalina.valves.CometConnectionManagerValve/1.0";
060:
061: /**
062: * The string manager for this package.
063: */
064: protected StringManager sm = StringManager
065: .getManager(Constants.Package);
066:
067: /**
068: * The lifecycle event support for this component.
069: */
070: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
071:
072: /**
073: * Has this component been started yet?
074: */
075: protected boolean started = false;
076:
077: /**
078: * Connection list.
079: */
080: protected ConcurrentHashMap<String, ConnectionInfo[]> connections = new ConcurrentHashMap<String, ConnectionInfo[]>();
081:
082: // ------------------------------------------------------------- Properties
083:
084: // ------------------------------------------------------ Lifecycle Methods
085:
086: /**
087: * Add a lifecycle event listener to this component.
088: *
089: * @param listener The listener to add
090: */
091: public void addLifecycleListener(LifecycleListener listener) {
092:
093: lifecycle.addLifecycleListener(listener);
094:
095: }
096:
097: /**
098: * Get the lifecycle listeners associated with this lifecycle. If this
099: * Lifecycle has no listeners registered, a zero-length array is returned.
100: */
101: public LifecycleListener[] findLifecycleListeners() {
102:
103: return lifecycle.findLifecycleListeners();
104:
105: }
106:
107: /**
108: * Remove a lifecycle event listener from this component.
109: *
110: * @param listener The listener to add
111: */
112: public void removeLifecycleListener(LifecycleListener listener) {
113:
114: lifecycle.removeLifecycleListener(listener);
115:
116: }
117:
118: /**
119: * Prepare for the beginning of active use of the public methods of this
120: * component. This method should be called after <code>configure()</code>,
121: * and before any of the public methods of the component are utilized.
122: *
123: * @exception LifecycleException if this component detects a fatal error
124: * that prevents this component from being used
125: */
126: public void start() throws LifecycleException {
127:
128: // Validate and update our current component state
129: if (started)
130: throw new LifecycleException(sm
131: .getString("semaphoreValve.alreadyStarted"));
132: lifecycle.fireLifecycleEvent(START_EVENT, null);
133: started = true;
134:
135: if (container instanceof Context) {
136: ((Lifecycle) container).addLifecycleListener(this );
137: }
138:
139: }
140:
141: /**
142: * Gracefully terminate the active use of the public methods of this
143: * component. This method should be the last one called on a given
144: * instance of this component.
145: *
146: * @exception LifecycleException if this component detects a fatal error
147: * that needs to be reported
148: */
149: public void stop() throws LifecycleException {
150:
151: // Validate and update our current component state
152: if (!started)
153: throw new LifecycleException(sm
154: .getString("semaphoreValve.notStarted"));
155: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
156: started = false;
157:
158: if (container instanceof Context) {
159: ((Lifecycle) container).removeLifecycleListener(this );
160: }
161:
162: // The webapp is getting stopped, so all current connections
163: // should be closed
164: // Close all Comet connections associated with this session
165: // Note: this will only be done if the container was not a Context
166: // (otherwise, this needs to be done before stop, as the servlet would
167: // be deallocated already)
168: Iterator<ConnectionInfo[]> iterator = connections.values()
169: .iterator();
170: while (iterator.hasNext()) {
171: ConnectionInfo[] connectionInfos = iterator.next();
172: if (connectionInfos != null) {
173: for (int i = 0; i < connectionInfos.length; i++) {
174: ConnectionInfo connectionInfo = connectionInfos[i];
175: try {
176: connectionInfo.event.close();
177: } catch (Exception e) {
178: container
179: .getLogger()
180: .warn(
181: sm
182: .getString("cometConnectionManagerValve.event"),
183: e);
184: }
185: }
186: }
187: }
188: connections.clear();
189:
190: }
191:
192: public void lifecycleEvent(LifecycleEvent event) {
193: if (event.getType() == Lifecycle.BEFORE_STOP_EVENT) {
194: // The webapp is getting stopped, so all current connections
195: // should be closed
196: // Close all Comet connections associated with this session
197: Iterator<ConnectionInfo[]> iterator = connections.values()
198: .iterator();
199: while (iterator.hasNext()) {
200: ConnectionInfo[] connectionInfos = iterator.next();
201: if (connectionInfos != null) {
202: for (int i = 0; i < connectionInfos.length; i++) {
203: ConnectionInfo connectionInfo = connectionInfos[i];
204: try {
205: ((CometEventImpl) connectionInfo.event)
206: .setEventType(CometEvent.EventType.END);
207: ((CometEventImpl) connectionInfo.event)
208: .setEventSubType(CometEvent.EventSubType.WEBAPP_RELOAD);
209: getNext().event(connectionInfo.request,
210: connectionInfo.response,
211: connectionInfo.event);
212: connectionInfo.event.close();
213: } catch (Exception e) {
214: container
215: .getLogger()
216: .warn(
217: sm
218: .getString("cometConnectionManagerValve.event"),
219: e);
220: }
221: }
222: }
223: }
224: connections.clear();
225: }
226: }
227:
228: // --------------------------------------------------------- Public Methods
229:
230: /**
231: * Return descriptive information about this Valve implementation.
232: */
233: public String getInfo() {
234: return (info);
235: }
236:
237: /**
238: * Register requests for tracking, whenever needed.
239: *
240: * @param request The servlet request to be processed
241: * @param response The servlet response to be created
242: *
243: * @exception IOException if an input/output error occurs
244: * @exception ServletException if a servlet error occurs
245: */
246: public void invoke(Request request, Response response)
247: throws IOException, ServletException {
248: // Perform the request
249: getNext().invoke(request, response);
250:
251: if (request.isComet() && !response.isClosed()) {
252: // Start tracking this connection, since this is a
253: // begin event, and Comet mode is on
254: HttpSession session = request.getSession(true);
255: ConnectionInfo newConnectionInfo = new ConnectionInfo();
256: newConnectionInfo.request = request;
257: newConnectionInfo.response = response;
258: newConnectionInfo.event = request.getEvent();
259: synchronized (session) {
260: String id = session.getId();
261: ConnectionInfo[] connectionInfos = connections.get(id);
262: if (connectionInfos == null) {
263: connectionInfos = new ConnectionInfo[1];
264: connectionInfos[0] = newConnectionInfo;
265: connections.put(id, connectionInfos);
266: } else {
267: ConnectionInfo[] newConnectionInfos = new ConnectionInfo[connectionInfos.length + 1];
268: for (int i = 0; i < connectionInfos.length; i++) {
269: newConnectionInfos[i] = connectionInfos[i];
270: }
271: newConnectionInfos[connectionInfos.length] = newConnectionInfo;
272: connections.put(id, newConnectionInfos);
273: }
274: }
275: }
276:
277: }
278:
279: /**
280: * Use events to update the connection state.
281: *
282: * @param request The servlet request to be processed
283: * @param response The servlet response to be created
284: *
285: * @exception IOException if an input/output error occurs
286: * @exception ServletException if a servlet error occurs
287: */
288: public void event(Request request, Response response,
289: CometEvent event) throws IOException, ServletException {
290:
291: // Perform the request
292: boolean ok = false;
293: try {
294: getNext().event(request, response, event);
295: ok = true;
296: } finally {
297: if (!ok
298: || response.isClosed()
299: || (event.getEventType() == CometEvent.EventType.END)
300: || (event.getEventType() == CometEvent.EventType.ERROR && !(event
301: .getEventSubType() == CometEvent.EventSubType.TIMEOUT))) {
302: // Remove from tracked list, the connection is done
303: HttpSession session = request.getSession(true);
304: synchronized (session) {
305: ConnectionInfo[] connectionInfos = connections
306: .get(session.getId());
307: if (connectionInfos != null) {
308: boolean found = false;
309: for (int i = 0; !found
310: && (i < connectionInfos.length); i++) {
311: found = (connectionInfos[i].request == request);
312: }
313: if (found) {
314: ConnectionInfo[] newConnectionInfos = new ConnectionInfo[connectionInfos.length - 1];
315: int pos = 0;
316: for (int i = 0; i < connectionInfos.length; i++) {
317: if (connectionInfos[i].request != request) {
318: newConnectionInfos[pos++] = connectionInfos[i];
319: }
320: }
321: connections.put(session.getId(),
322: newConnectionInfos);
323: }
324: }
325: }
326: }
327: }
328:
329: }
330:
331: public void sessionCreated(HttpSessionEvent se) {
332: }
333:
334: public void sessionDestroyed(HttpSessionEvent se) {
335: // Close all Comet connections associated with this session
336: ConnectionInfo[] connectionInfos = connections.remove(se
337: .getSession().getId());
338: if (connectionInfos != null) {
339: for (int i = 0; i < connectionInfos.length; i++) {
340: ConnectionInfo connectionInfo = connectionInfos[i];
341: try {
342: ((CometEventImpl) connectionInfo.event)
343: .setEventType(CometEvent.EventType.END);
344: ((CometEventImpl) connectionInfo.event)
345: .setEventSubType(CometEvent.EventSubType.SESSION_END);
346: getNext().event(connectionInfo.request,
347: connectionInfo.response,
348: connectionInfo.event);
349: connectionInfo.event.close();
350: } catch (Exception e) {
351: container
352: .getLogger()
353: .warn(
354: sm
355: .getString("cometConnectionManagerValve.event"),
356: e);
357: }
358: }
359: }
360: }
361:
362: // --------------------------------------------- ConnectionInfo Inner Class
363:
364: protected class ConnectionInfo {
365: public CometEvent event;
366: public Request request;
367: public Response response;
368: }
369:
370: }
|