001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.safehaus.asyncweb.service.session;
021:
022: import java.util.ArrayList;
023: import java.util.Collection;
024: import java.util.Collections;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029:
030: import org.slf4j.Logger;
031: import org.slf4j.LoggerFactory;
032: import org.safehaus.asyncweb.service.HttpSession;
033: import org.safehaus.asyncweb.util.LinkedPermitIssuer;
034: import org.safehaus.asyncweb.util.PermitExpirationListener;
035: import org.safehaus.asyncweb.util.TimedPermit;
036: import org.safehaus.asyncweb.util.TimedPermitIssuer;
037:
038: /**
039: * A simple <code>SessionStore</code> implementation which holds all session
040: * data in memory. A <code>TimedPermitIssuer</code> is employed to issue a time-out
041: * permit for each issued session.
042: *
043: * @author irvingd
044: *
045: */
046: public class BasicSessionStore implements HttpSessionStore {
047:
048: private static final Logger LOG = LoggerFactory
049: .getLogger(BasicSessionStore.class);
050:
051: /**
052: * Default session timeout of 15 minutes
053: */
054: private static final long DEFAULT_SESSION_TIMEOUT = 900000;
055:
056: private Map<String, HttpSession> sessionMap = Collections
057: .synchronizedMap(new HashMap<String, HttpSession>());
058: private List<HttpSessionListener> listeners = Collections
059: .synchronizedList(new ArrayList<HttpSessionListener>());
060:
061: private TimedPermitIssuer permitIssuer;
062: private boolean isClosed;
063:
064: /**
065: * Constructs with the default session timeout
066: */
067: public BasicSessionStore() {
068: this (DEFAULT_SESSION_TIMEOUT);
069: }
070:
071: /**
072: * Constructs with a specified session timeout
073: *
074: * @param sessionTimeout The session timeout (in ms)
075: */
076: public BasicSessionStore(long sessionTimeout) {
077: permitIssuer = new LinkedPermitIssuer(sessionTimeout);
078: permitIssuer.addPermitExpirationListener(new TimeoutListener());
079: LOG.info("BasicSessionStore timeout: " + sessionTimeout + "ms");
080: }
081:
082: /**
083: * Adds a listener to this store
084: *
085: * @param listener The listener to add
086: */
087: public void addSessionListener(HttpSessionListener listener) {
088: listeners.add(listener);
089: }
090:
091: /**
092: * Sets the listeners employed by this store.
093: * Any existing listeners are removed
094: *
095: * @param listeners The listeners to be added
096: */
097: public void setSessionListeners(
098: Collection<HttpSessionListener> listeners) {
099: synchronized (this .listeners) {
100: this .listeners.clear();
101: this .listeners.addAll(listeners);
102: }
103: }
104:
105: /**
106: * Closes this store.
107: * Our permit issuer is closed, and all sessions are destroyed.
108: */
109: public void close() {
110: List<HttpSession> closureList = null;
111:
112: synchronized (sessionMap) {
113: if (isClosed) {
114: LOG.debug("Already closed");
115: return;
116: }
117: LOG.debug("BasicSessionStore closing");
118: permitIssuer.close();
119: isClosed = true;
120: closureList = new ArrayList<HttpSession>(sessionMap
121: .values());
122: }
123: for (Iterator<HttpSession> iter = closureList.iterator(); iter
124: .hasNext();) {
125: BasicSession session = (BasicSession) iter.next();
126: LOG
127: .debug("Closure: Destroying session: "
128: + session.getId());
129: session.destroy();
130: }
131: }
132:
133: /**
134: * Creates a new session for the specified key.
135: *
136: * @param key The session key
137: * @return The created session, or <code>null</code> if a session is already
138: * held for the specified key
139: */
140: public HttpSession createSession(String key) {
141: BasicSession created = null;
142: synchronized (sessionMap) {
143: if (isClosed) {
144: throw new IllegalStateException("Store closed");
145: }
146: if (!sessionMap.containsKey(key)) {
147: created = new BasicSession(key, this );
148: sessionMap.put(key, created);
149: TimedPermit permit = permitIssuer.issuePermit(created);
150: created.setPermit(permit);
151: }
152: }
153: if (created != null) {
154: if (LOG.isDebugEnabled()) {
155: LOG.debug("New session created with key '" + key
156: + "'. Firing notifications");
157: }
158: fireCreated(created);
159: }
160: return created;
161: }
162:
163: /**
164: * Locates the session with the specified key.
165: * If the session is found, we request it to renew its access permit.
166: *
167: * @param key The key for which a session is required
168: * @return The located session, or <code>null</code> if no session with the
169: * specified key was found
170: */
171: public HttpSession locateSession(String key) {
172: BasicSession session = (BasicSession) sessionMap.get(key);
173: if (session != null) {
174: if (LOG.isDebugEnabled()) {
175: LOG.debug("Located session with key '" + key
176: + "'. Marking as accessed");
177: }
178: session.access();
179: }
180: return session;
181: }
182:
183: /**
184: * Invoked by a session we created when it successfully processes an expiry.
185: * The session is removed from our session map, and expiry notifications are fired.
186: *
187: * @param session The expired session
188: */
189: void sessionExpired(BasicSession session) {
190: if (LOG.isDebugEnabled()) {
191: LOG
192: .debug("Session has been expired. Processing notifications for '"
193: + session.getId() + "'");
194: }
195: sessionMap.remove(session.getId());
196: fireExpiry(session);
197: }
198:
199: /**
200: * Invoked by a session we created when it successfully processes a destruction
201: * request.
202: * The session is removed from our session map, and destruction notifications
203: * are fired
204: *
205: * @param session The destroyed session
206: */
207: void sessionDestroyed(BasicSession session) {
208: if (LOG.isDebugEnabled()) {
209: LOG
210: .debug("Session has been destroyed. Processing notifications for '"
211: + session.getId() + "'");
212: }
213: sessionMap.remove(session.getId());
214: fireDestroyed(session);
215: }
216:
217: /**
218: * Invoked when the permit associated with the specified session expires.
219: * We simply request the session to expire itself.
220: * If the session is not already destroyed, it will request us to fire
221: * notifications on its behalf.
222: *
223: * @param session The expired session
224: */
225: private void sessionPermitExpired(BasicSession session) {
226: session.expire();
227: }
228:
229: /**
230: * Fires creation notification to all listeners assocaited with this store
231: *
232: * @param session The expired session
233: */
234: private void fireCreated(HttpSession session) {
235: synchronized (listeners) {
236: for (Iterator iter = listeners.iterator(); iter.hasNext();) {
237: HttpSessionListener listener = (HttpSessionListener) iter
238: .next();
239: listener.sessionCreated(session);
240: }
241: }
242: }
243:
244: /**
245: * Fires destruction notification to all listeners assocaited with this store
246: *
247: * @param session The expired session
248: */
249: private void fireDestroyed(HttpSession session) {
250: synchronized (listeners) {
251: for (Iterator iter = listeners.iterator(); iter.hasNext();) {
252: HttpSessionListener listener = (HttpSessionListener) iter
253: .next();
254: listener.sessionDestroyed(session);
255: }
256: }
257: }
258:
259: /**
260: * Fires expiry notification to all listeners assocaited with this store
261: *
262: * @param session The expired session
263: */
264: private void fireExpiry(HttpSession session) {
265: synchronized (listeners) {
266: for (Iterator iter = listeners.iterator(); iter.hasNext();) {
267: HttpSessionListener listener = (HttpSessionListener) iter
268: .next();
269: listener.sessionExpired(session);
270: }
271: }
272: }
273:
274: /**
275: * Receives notifications of timed out permits issued by this store,
276: * and triggers expiry of the associated session
277: *
278: * @author irvingd
279: *
280: */
281: private class TimeoutListener implements PermitExpirationListener {
282:
283: /**
284: * Invoked when a permit issued for a session expires
285: *
286: * @param session The session which has expired
287: */
288: public void permitExpired(Object session) {
289: sessionPermitExpired((BasicSession) session);
290: }
291:
292: }
293:
294: }
|