001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone;
008:
009: import java.io.File;
010: import java.io.FileInputStream;
011: import java.io.FileOutputStream;
012: import java.io.IOException;
013: import java.io.InputStream;
014: import java.io.ObjectInputStream;
015: import java.io.ObjectOutputStream;
016: import java.io.OutputStream;
017: import java.io.Serializable;
018: import java.util.ArrayList;
019: import java.util.Collections;
020: import java.util.Enumeration;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.Hashtable;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027: import java.util.Set;
028:
029: import javax.servlet.ServletContext;
030: import javax.servlet.http.HttpSession;
031: import javax.servlet.http.HttpSessionActivationListener;
032: import javax.servlet.http.HttpSessionAttributeListener;
033: import javax.servlet.http.HttpSessionBindingEvent;
034: import javax.servlet.http.HttpSessionBindingListener;
035: import javax.servlet.http.HttpSessionEvent;
036: import javax.servlet.http.HttpSessionListener;
037:
038: /**
039: * Http session implementation for Winstone.
040: *
041: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
042: * @version $Id: WinstoneSession.java,v 1.10 2006/08/27 07:19:47 rickknowles Exp $
043: */
044: public class WinstoneSession implements HttpSession, Serializable {
045: public static final String SESSION_COOKIE_NAME = "JSESSIONID";
046:
047: private String sessionId;
048: private WebAppConfiguration webAppConfig;
049: private Map sessionData;
050: private long createTime;
051: private long lastAccessedTime;
052: private int maxInactivePeriod;
053: private boolean isNew;
054: private boolean isInvalidated;
055: private HttpSessionAttributeListener sessionAttributeListeners[];
056: private HttpSessionListener sessionListeners[];
057: private HttpSessionActivationListener sessionActivationListeners[];
058: private boolean distributable;
059: private Object sessionMonitor = new Boolean(true);
060: private Set requestsUsingMe;
061:
062: /**
063: * Constructor
064: */
065: public WinstoneSession(String sessionId) {
066: this .sessionId = sessionId;
067: this .sessionData = new HashMap();
068: this .requestsUsingMe = new HashSet();
069: this .createTime = System.currentTimeMillis();
070: this .isNew = true;
071: this .isInvalidated = false;
072: }
073:
074: public void setWebAppConfiguration(WebAppConfiguration webAppConfig) {
075: this .webAppConfig = webAppConfig;
076: this .distributable = webAppConfig.isDistributable();
077: }
078:
079: public void sendCreatedNotifies() {
080: // Notify session listeners of new session
081: for (int n = 0; n < this .sessionListeners.length; n++) {
082: ClassLoader cl = Thread.currentThread()
083: .getContextClassLoader();
084: Thread.currentThread().setContextClassLoader(
085: this .webAppConfig.getLoader());
086: this .sessionListeners[n]
087: .sessionCreated(new HttpSessionEvent(this ));
088: Thread.currentThread().setContextClassLoader(cl);
089: }
090: }
091:
092: public void setSessionActivationListeners(
093: HttpSessionActivationListener listeners[]) {
094: this .sessionActivationListeners = listeners;
095: }
096:
097: public void setSessionAttributeListeners(
098: HttpSessionAttributeListener listeners[]) {
099: this .sessionAttributeListeners = listeners;
100: }
101:
102: public void setSessionListeners(HttpSessionListener listeners[]) {
103: this .sessionListeners = listeners;
104: }
105:
106: public void setLastAccessedDate(long time) {
107: this .lastAccessedTime = time;
108: }
109:
110: public void setIsNew(boolean isNew) {
111: this .isNew = isNew;
112: }
113:
114: public void addUsed(WinstoneRequest request) {
115: this .requestsUsingMe.add(request);
116: }
117:
118: public void removeUsed(WinstoneRequest request) {
119: this .requestsUsingMe.remove(request);
120: }
121:
122: public boolean isUnusedByRequests() {
123: return this .requestsUsingMe.isEmpty();
124: }
125:
126: public boolean isExpired() {
127: // check if it's expired yet
128: long nowDate = System.currentTimeMillis();
129: long maxInactive = getMaxInactiveInterval() * 1000;
130: return ((maxInactive > 0) && (nowDate - this .lastAccessedTime > maxInactive));
131: }
132:
133: // Implementation methods
134: public Object getAttribute(String name) {
135: if (this .isInvalidated) {
136: throw new IllegalStateException(Launcher.RESOURCES
137: .getString("WinstoneSession.InvalidatedSession"));
138: }
139: Object att = null;
140: synchronized (this .sessionMonitor) {
141: att = this .sessionData.get(name);
142: }
143: return att;
144: }
145:
146: public Enumeration getAttributeNames() {
147: if (this .isInvalidated) {
148: throw new IllegalStateException(Launcher.RESOURCES
149: .getString("WinstoneSession.InvalidatedSession"));
150: }
151: Enumeration names = null;
152: synchronized (this .sessionMonitor) {
153: names = Collections.enumeration(this .sessionData.keySet());
154: }
155: return names;
156: }
157:
158: public void setAttribute(String name, Object value) {
159: if (this .isInvalidated) {
160: throw new IllegalStateException(Launcher.RESOURCES
161: .getString("WinstoneSession.InvalidatedSession"));
162: }
163: // Check for serializability if distributable
164: if (this .distributable && (value != null)
165: && !(value instanceof java.io.Serializable))
166: throw new IllegalArgumentException(Launcher.RESOURCES
167: .getString(
168: "WinstoneSession.AttributeNotSerializable",
169: new String[] { name,
170: value.getClass().getName() }));
171:
172: // valueBound must be before binding
173: if (value instanceof HttpSessionBindingListener) {
174: HttpSessionBindingListener hsbl = (HttpSessionBindingListener) value;
175: ClassLoader cl = Thread.currentThread()
176: .getContextClassLoader();
177: Thread.currentThread().setContextClassLoader(
178: this .webAppConfig.getLoader());
179: hsbl.valueBound(new HttpSessionBindingEvent(this , name,
180: value));
181: Thread.currentThread().setContextClassLoader(cl);
182: }
183:
184: Object oldValue = null;
185: synchronized (this .sessionMonitor) {
186: oldValue = this .sessionData.get(name);
187: if (value == null) {
188: this .sessionData.remove(name);
189: } else {
190: this .sessionData.put(name, value);
191: }
192: }
193:
194: // valueUnbound must be after unbinding
195: if (oldValue instanceof HttpSessionBindingListener) {
196: HttpSessionBindingListener hsbl = (HttpSessionBindingListener) oldValue;
197: ClassLoader cl = Thread.currentThread()
198: .getContextClassLoader();
199: Thread.currentThread().setContextClassLoader(
200: this .webAppConfig.getLoader());
201: hsbl.valueUnbound(new HttpSessionBindingEvent(this , name,
202: oldValue));
203: Thread.currentThread().setContextClassLoader(cl);
204: }
205:
206: // Notify other listeners
207: if (oldValue != null)
208: for (int n = 0; n < this .sessionAttributeListeners.length; n++) {
209: ClassLoader cl = Thread.currentThread()
210: .getContextClassLoader();
211: Thread.currentThread().setContextClassLoader(
212: this .webAppConfig.getLoader());
213: this .sessionAttributeListeners[n]
214: .attributeReplaced(new HttpSessionBindingEvent(
215: this , name, oldValue));
216: Thread.currentThread().setContextClassLoader(cl);
217: }
218:
219: else
220: for (int n = 0; n < this .sessionAttributeListeners.length; n++) {
221: ClassLoader cl = Thread.currentThread()
222: .getContextClassLoader();
223: Thread.currentThread().setContextClassLoader(
224: this .webAppConfig.getLoader());
225: this .sessionAttributeListeners[n]
226: .attributeAdded(new HttpSessionBindingEvent(
227: this , name, value));
228: Thread.currentThread().setContextClassLoader(cl);
229:
230: }
231: }
232:
233: public void removeAttribute(String name) {
234: if (this .isInvalidated) {
235: throw new IllegalStateException(Launcher.RESOURCES
236: .getString("WinstoneSession.InvalidatedSession"));
237: }
238: Object value = null;
239: synchronized (this .sessionMonitor) {
240: value = this .sessionData.get(name);
241: this .sessionData.remove(name);
242: }
243:
244: // Notify listeners
245: if (value instanceof HttpSessionBindingListener) {
246: HttpSessionBindingListener hsbl = (HttpSessionBindingListener) value;
247: ClassLoader cl = Thread.currentThread()
248: .getContextClassLoader();
249: Thread.currentThread().setContextClassLoader(
250: this .webAppConfig.getLoader());
251: hsbl.valueUnbound(new HttpSessionBindingEvent(this , name));
252: Thread.currentThread().setContextClassLoader(cl);
253: }
254: if (value != null)
255: for (int n = 0; n < this .sessionAttributeListeners.length; n++) {
256: ClassLoader cl = Thread.currentThread()
257: .getContextClassLoader();
258: Thread.currentThread().setContextClassLoader(
259: this .webAppConfig.getLoader());
260: this .sessionAttributeListeners[n]
261: .attributeRemoved(new HttpSessionBindingEvent(
262: this , name, value));
263: Thread.currentThread().setContextClassLoader(cl);
264: }
265: }
266:
267: public long getCreationTime() {
268: if (this .isInvalidated) {
269: throw new IllegalStateException(Launcher.RESOURCES
270: .getString("WinstoneSession.InvalidatedSession"));
271: }
272: return this .createTime;
273: }
274:
275: public long getLastAccessedTime() {
276: if (this .isInvalidated) {
277: throw new IllegalStateException(Launcher.RESOURCES
278: .getString("WinstoneSession.InvalidatedSession"));
279: }
280: return this .lastAccessedTime;
281: }
282:
283: public String getId() {
284: return this .sessionId;
285: }
286:
287: public int getMaxInactiveInterval() {
288: return this .maxInactivePeriod;
289: }
290:
291: public void setMaxInactiveInterval(int interval) {
292: this .maxInactivePeriod = interval;
293: }
294:
295: public boolean isNew() {
296: if (this .isInvalidated) {
297: throw new IllegalStateException(Launcher.RESOURCES
298: .getString("WinstoneSession.InvalidatedSession"));
299: }
300: return this .isNew;
301: }
302:
303: public ServletContext getServletContext() {
304: return this .webAppConfig;
305: }
306:
307: public void invalidate() {
308: if (this .isInvalidated) {
309: throw new IllegalStateException(Launcher.RESOURCES
310: .getString("WinstoneSession.InvalidatedSession"));
311: }
312: // Notify session listeners of invalidated session -- backwards
313: for (int n = this .sessionListeners.length - 1; n >= 0; n--) {
314: ClassLoader cl = Thread.currentThread()
315: .getContextClassLoader();
316: Thread.currentThread().setContextClassLoader(
317: this .webAppConfig.getLoader());
318: this .sessionListeners[n]
319: .sessionDestroyed(new HttpSessionEvent(this ));
320: Thread.currentThread().setContextClassLoader(cl);
321: }
322:
323: List keys = new ArrayList(this .sessionData.keySet());
324: for (Iterator i = keys.iterator(); i.hasNext();)
325: removeAttribute((String) i.next());
326: synchronized (this .sessionMonitor) {
327: this .sessionData.clear();
328: }
329: this .isInvalidated = true;
330: this .webAppConfig.removeSessionById(this .sessionId);
331: }
332:
333: /**
334: * Called after the session has been serialized to another server.
335: */
336: public void passivate() {
337: // Notify session listeners of invalidated session
338: for (int n = 0; n < this .sessionActivationListeners.length; n++) {
339: ClassLoader cl = Thread.currentThread()
340: .getContextClassLoader();
341: Thread.currentThread().setContextClassLoader(
342: this .webAppConfig.getLoader());
343: this .sessionActivationListeners[n]
344: .sessionWillPassivate(new HttpSessionEvent(this ));
345: Thread.currentThread().setContextClassLoader(cl);
346: }
347:
348: // Question: Is passivation equivalent to invalidation ? Should all
349: // entries be removed ?
350: // List keys = new ArrayList(this.sessionData.keySet());
351: // for (Iterator i = keys.iterator(); i.hasNext(); )
352: // removeAttribute((String) i.next());
353: synchronized (this .sessionMonitor) {
354: this .sessionData.clear();
355: }
356: this .webAppConfig.removeSessionById(this .sessionId);
357: }
358:
359: /**
360: * Called after the session has been deserialized from another server.
361: */
362: public void activate(WebAppConfiguration webAppConfig) {
363: this .webAppConfig = webAppConfig;
364: webAppConfig.setSessionListeners(this );
365:
366: // Notify session listeners of invalidated session
367: for (int n = 0; n < this .sessionActivationListeners.length; n++) {
368: ClassLoader cl = Thread.currentThread()
369: .getContextClassLoader();
370: Thread.currentThread().setContextClassLoader(
371: this .webAppConfig.getLoader());
372: this .sessionActivationListeners[n]
373: .sessionDidActivate(new HttpSessionEvent(this ));
374: Thread.currentThread().setContextClassLoader(cl);
375: }
376: }
377:
378: /**
379: * Save this session to the temp dir defined for this webapp
380: */
381: public void saveToTemp() {
382: File toDir = getSessionTempDir(this .webAppConfig);
383: synchronized (this .sessionMonitor) {
384: OutputStream out = null;
385: ObjectOutputStream objOut = null;
386: try {
387: File toFile = new File(toDir, this .sessionId + ".ser");
388: out = new FileOutputStream(toFile, false);
389: objOut = new ObjectOutputStream(out);
390: objOut.writeObject(this );
391: } catch (IOException err) {
392: Logger.log(Logger.ERROR, Launcher.RESOURCES,
393: "WinstoneSession.ErrorSavingSession", err);
394: } finally {
395: if (objOut != null) {
396: try {
397: objOut.close();
398: } catch (IOException err) {
399: }
400: }
401: if (out != null) {
402: try {
403: out.close();
404: } catch (IOException err) {
405: }
406: }
407: }
408: }
409: }
410:
411: public static File getSessionTempDir(
412: WebAppConfiguration webAppConfig) {
413: File tmpDir = (File) webAppConfig
414: .getAttribute("javax.servlet.context.tempdir");
415: File sessionsDir = new File(tmpDir, "WEB-INF" + File.separator
416: + "winstoneSessions");
417: if (!sessionsDir.exists()) {
418: sessionsDir.mkdirs();
419: }
420: return sessionsDir;
421: }
422:
423: public static void loadSessions(WebAppConfiguration webAppConfig) {
424: int expiredCount = 0;
425: // Iterate through the files in the dir, instantiate and then add to the sessions set
426: File tempDir = getSessionTempDir(webAppConfig);
427: File possibleSessionFiles[] = tempDir.listFiles();
428: for (int n = 0; n < possibleSessionFiles.length; n++) {
429: if (possibleSessionFiles[n].getName().endsWith(".ser")) {
430: InputStream in = null;
431: ObjectInputStream objIn = null;
432: try {
433: in = new FileInputStream(possibleSessionFiles[n]);
434: objIn = new ObjectInputStream(in);
435: WinstoneSession session = (WinstoneSession) objIn
436: .readObject();
437: session.setWebAppConfiguration(webAppConfig);
438: webAppConfig.setSessionListeners(session);
439: if (session.isExpired()) {
440: session.invalidate();
441: expiredCount++;
442: } else {
443: webAppConfig.addSession(session.getId(),
444: session);
445: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
446: "WinstoneSession.RestoredSession",
447: session.getId());
448: }
449: } catch (Throwable err) {
450: Logger.log(Logger.ERROR, Launcher.RESOURCES,
451: "WinstoneSession.ErrorLoadingSession", err);
452: } finally {
453: if (objIn != null) {
454: try {
455: objIn.close();
456: } catch (IOException err) {
457: }
458: }
459: if (in != null) {
460: try {
461: in.close();
462: } catch (IOException err) {
463: }
464: }
465: possibleSessionFiles[n].delete();
466: }
467: }
468: }
469: if (expiredCount > 0) {
470: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
471: "WebAppConfig.InvalidatedSessions", expiredCount
472: + "");
473: }
474: }
475:
476: /**
477: * Serialization implementation. This makes sure to only serialize the parts
478: * we want to send to another server.
479: *
480: * @param out
481: * The stream to write the contents to
482: * @throws IOException
483: */
484: private void writeObject(java.io.ObjectOutputStream out)
485: throws IOException {
486: out.writeUTF(sessionId);
487: out.writeLong(createTime);
488: out.writeLong(lastAccessedTime);
489: out.writeInt(maxInactivePeriod);
490: out.writeBoolean(isNew);
491: out.writeBoolean(distributable);
492:
493: // Write the map, but first remove non-serializables
494: Map copy = new HashMap(sessionData);
495: Set keys = new HashSet(copy.keySet());
496: for (Iterator i = keys.iterator(); i.hasNext();) {
497: String key = (String) i.next();
498: if (!(copy.get(key) instanceof Serializable)) {
499: Logger.log(Logger.WARNING, Launcher.RESOURCES,
500: "WinstoneSession.SkippingNonSerializable",
501: new String[] { key,
502: copy.get(key).getClass().getName() });
503: }
504: copy.remove(key);
505: }
506: out.writeInt(copy.size());
507: for (Iterator i = copy.keySet().iterator(); i.hasNext();) {
508: String key = (String) i.next();
509: out.writeUTF(key);
510: out.writeObject(copy.get(key));
511: }
512: }
513:
514: /**
515: * Deserialization implementation
516: *
517: * @param in
518: * The source of stream data
519: * @throws IOException
520: * @throws ClassNotFoundException
521: */
522: private void readObject(java.io.ObjectInputStream in)
523: throws IOException, ClassNotFoundException {
524: this .sessionId = in.readUTF();
525: this .createTime = in.readLong();
526: this .lastAccessedTime = in.readLong();
527: this .maxInactivePeriod = in.readInt();
528: this .isNew = in.readBoolean();
529: this .distributable = in.readBoolean();
530:
531: // Read the map
532: this .sessionData = new Hashtable();
533: this .requestsUsingMe = new HashSet();
534: int entryCount = in.readInt();
535: for (int n = 0; n < entryCount; n++) {
536: String key = in.readUTF();
537: Object variable = in.readObject();
538: this .sessionData.put(key, variable);
539: }
540: this .sessionMonitor = new Boolean(true);
541: }
542:
543: /**
544: * @deprecated
545: */
546: public Object getValue(String name) {
547: return getAttribute(name);
548: }
549:
550: /**
551: * @deprecated
552: */
553: public void putValue(String name, Object value) {
554: setAttribute(name, value);
555: }
556:
557: /**
558: * @deprecated
559: */
560: public void removeValue(String name) {
561: removeAttribute(name);
562: }
563:
564: /**
565: * @deprecated
566: */
567: public String[] getValueNames() {
568: return (String[]) this .sessionData.keySet().toArray(
569: new String[0]);
570: }
571:
572: /**
573: * @deprecated
574: */
575: public javax.servlet.http.HttpSessionContext getSessionContext() {
576: return null;
577: } // deprecated
578: }
|