/* * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net> * Distributed under the terms of either: * - the common development and distribution license (CDDL), v1.0; or * - the GNU Lesser General Public License, v2.1 or later */ package winstone; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; /** * Http session implementation for Winstone. * * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a> * @version $Id: WinstoneSession.java,v 1.10 2006/08/27 07:19:47 rickknowles Exp $ */ public class WinstoneSession implements HttpSession, Serializable { public static final String SESSION_COOKIE_NAME = "JSESSIONID"; private String sessionId; private WebAppConfiguration webAppConfig; private Map sessionData; private long createTime; private long lastAccessedTime; private int maxInactivePeriod; private boolean isNew; private boolean isInvalidated; private HttpSessionAttributeListener sessionAttributeListeners[]; private HttpSessionListener sessionListeners[]; private HttpSessionActivationListener sessionActivationListeners[]; private boolean distributable; private Object sessionMonitor = new Boolean(true); private Set requestsUsingMe; /** * Constructor */ public WinstoneSession(String sessionId) { this.sessionId = sessionId; this.sessionData = new HashMap(); this.requestsUsingMe = new HashSet(); this.createTime = System.currentTimeMillis(); this.isNew = true; this.isInvalidated = false; } public void setWebAppConfiguration(WebAppConfiguration webAppConfig) { this.webAppConfig = webAppConfig; this.distributable = webAppConfig.isDistributable(); } public void sendCreatedNotifies() { // Notify session listeners of new session for (int n = 0; n < this.sessionListeners.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); this.sessionListeners[n].sessionCreated(new HttpSessionEvent(this)); Thread.currentThread().setContextClassLoader(cl); } } public void setSessionActivationListeners( HttpSessionActivationListener listeners[]) { this.sessionActivationListeners = listeners; } public void setSessionAttributeListeners( HttpSessionAttributeListener listeners[]) { this.sessionAttributeListeners = listeners; } public void setSessionListeners(HttpSessionListener listeners[]) { this.sessionListeners = listeners; } public void setLastAccessedDate(long time) { this.lastAccessedTime = time; } public void setIsNew(boolean isNew) { this.isNew = isNew; } public void addUsed(WinstoneRequest request) { this.requestsUsingMe.add(request); } public void removeUsed(WinstoneRequest request) { this.requestsUsingMe.remove(request); } public boolean isUnusedByRequests() { return this.requestsUsingMe.isEmpty(); } public boolean isExpired() { // check if it's expired yet long nowDate = System.currentTimeMillis(); long maxInactive = getMaxInactiveInterval() * 1000; return ((maxInactive > 0) && (nowDate - this.lastAccessedTime > maxInactive )); } // Implementation methods public Object getAttribute(String name) { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } Object att = null; synchronized (this.sessionMonitor) { att = this.sessionData.get(name); } return att; } public Enumeration getAttributeNames() { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } Enumeration names = null; synchronized (this.sessionMonitor) { names = Collections.enumeration(this.sessionData.keySet()); } return names; } public void setAttribute(String name, Object value) { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } // Check for serializability if distributable if (this.distributable && (value != null) && !(value instanceof java.io.Serializable)) throw new IllegalArgumentException(Launcher.RESOURCES.getString( "WinstoneSession.AttributeNotSerializable", new String[] { name, value.getClass().getName() })); // valueBound must be before binding if (value instanceof HttpSessionBindingListener) { HttpSessionBindingListener hsbl = (HttpSessionBindingListener) value; ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); hsbl.valueBound(new HttpSessionBindingEvent(this, name, value)); Thread.currentThread().setContextClassLoader(cl); } Object oldValue = null; synchronized (this.sessionMonitor) { oldValue = this.sessionData.get(name); if (value == null) { this.sessionData.remove(name); } else { this.sessionData.put(name, value); } } // valueUnbound must be after unbinding if (oldValue instanceof HttpSessionBindingListener) { HttpSessionBindingListener hsbl = (HttpSessionBindingListener) oldValue; ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); hsbl.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue)); Thread.currentThread().setContextClassLoader(cl); } // Notify other listeners if (oldValue != null) for (int n = 0; n < this.sessionAttributeListeners.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); this.sessionAttributeListeners[n].attributeReplaced( new HttpSessionBindingEvent(this, name, oldValue)); Thread.currentThread().setContextClassLoader(cl); } else for (int n = 0; n < this.sessionAttributeListeners.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); this.sessionAttributeListeners[n].attributeAdded( new HttpSessionBindingEvent(this, name, value)); Thread.currentThread().setContextClassLoader(cl); } } public void removeAttribute(String name) { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } Object value = null; synchronized (this.sessionMonitor) { value = this.sessionData.get(name); this.sessionData.remove(name); } // Notify listeners if (value instanceof HttpSessionBindingListener) { HttpSessionBindingListener hsbl = (HttpSessionBindingListener) value; ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); hsbl.valueUnbound(new HttpSessionBindingEvent(this, name)); Thread.currentThread().setContextClassLoader(cl); } if (value != null) for (int n = 0; n < this.sessionAttributeListeners.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); this.sessionAttributeListeners[n].attributeRemoved( new HttpSessionBindingEvent(this, name, value)); Thread.currentThread().setContextClassLoader(cl); } } public long getCreationTime() { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } return this.createTime; } public long getLastAccessedTime() { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } return this.lastAccessedTime; } public String getId() { return this.sessionId; } public int getMaxInactiveInterval() { return this.maxInactivePeriod; } public void setMaxInactiveInterval(int interval) { this.maxInactivePeriod = interval; } public boolean isNew() { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } return this.isNew; } public ServletContext getServletContext() { return this.webAppConfig; } public void invalidate() { if (this.isInvalidated) { throw new IllegalStateException(Launcher.RESOURCES.getString("WinstoneSession.InvalidatedSession")); } // Notify session listeners of invalidated session -- backwards for (int n = this.sessionListeners.length - 1; n >= 0; n--) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); this.sessionListeners[n].sessionDestroyed(new HttpSessionEvent(this)); Thread.currentThread().setContextClassLoader(cl); } List keys = new ArrayList(this.sessionData.keySet()); for (Iterator i = keys.iterator(); i.hasNext();) removeAttribute((String) i.next()); synchronized (this.sessionMonitor) { this.sessionData.clear(); } this.isInvalidated = true; this.webAppConfig.removeSessionById(this.sessionId); } /** * Called after the session has been serialized to another server. */ public void passivate() { // Notify session listeners of invalidated session for (int n = 0; n < this.sessionActivationListeners.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); this.sessionActivationListeners[n].sessionWillPassivate( new HttpSessionEvent(this)); Thread.currentThread().setContextClassLoader(cl); } // Question: Is passivation equivalent to invalidation ? Should all // entries be removed ? // List keys = new ArrayList(this.sessionData.keySet()); // for (Iterator i = keys.iterator(); i.hasNext(); ) // removeAttribute((String) i.next()); synchronized (this.sessionMonitor) { this.sessionData.clear(); } this.webAppConfig.removeSessionById(this.sessionId); } /** * Called after the session has been deserialized from another server. */ public void activate(WebAppConfiguration webAppConfig) { this.webAppConfig = webAppConfig; webAppConfig.setSessionListeners(this); // Notify session listeners of invalidated session for (int n = 0; n < this.sessionActivationListeners.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this.webAppConfig.getLoader()); this.sessionActivationListeners[n].sessionDidActivate( new HttpSessionEvent(this)); Thread.currentThread().setContextClassLoader(cl); } } /** * Save this session to the temp dir defined for this webapp */ public void saveToTemp() { File toDir = getSessionTempDir(this.webAppConfig); synchronized (this.sessionMonitor) { OutputStream out = null; ObjectOutputStream objOut = null; try { File toFile = new File(toDir, this.sessionId + ".ser"); out = new FileOutputStream(toFile, false); objOut = new ObjectOutputStream(out); objOut.writeObject(this); } catch (IOException err) { Logger.log(Logger.ERROR, Launcher.RESOURCES, "WinstoneSession.ErrorSavingSession", err); } finally { if (objOut != null) { try {objOut.close();} catch (IOException err) {} } if (out != null) { try {out.close();} catch (IOException err) {} } } } } public static File getSessionTempDir(WebAppConfiguration webAppConfig) { File tmpDir = (File) webAppConfig.getAttribute("javax.servlet.context.tempdir"); File sessionsDir = new File(tmpDir, "WEB-INF" + File.separator + "winstoneSessions"); if (!sessionsDir.exists()) { sessionsDir.mkdirs(); } return sessionsDir; } public static void loadSessions(WebAppConfiguration webAppConfig) { int expiredCount = 0; // Iterate through the files in the dir, instantiate and then add to the sessions set File tempDir = getSessionTempDir(webAppConfig); File possibleSessionFiles[] = tempDir.listFiles(); for (int n = 0; n < possibleSessionFiles.length; n++) { if (possibleSessionFiles[n].getName().endsWith(".ser")) { InputStream in = null; ObjectInputStream objIn = null; try { in = new FileInputStream(possibleSessionFiles[n]); objIn = new ObjectInputStream(in); WinstoneSession session = (WinstoneSession) objIn.readObject(); session.setWebAppConfiguration(webAppConfig); webAppConfig.setSessionListeners(session); if (session.isExpired()) { session.invalidate(); expiredCount++; } else { webAppConfig.addSession(session.getId(), session); Logger.log(Logger.DEBUG, Launcher.RESOURCES, "WinstoneSession.RestoredSession", session.getId()); } } catch (Throwable err) { Logger.log(Logger.ERROR, Launcher.RESOURCES, "WinstoneSession.ErrorLoadingSession", err); } finally { if (objIn != null) { try {objIn.close();} catch (IOException err) {} } if (in != null) { try {in.close();} catch (IOException err) {} } possibleSessionFiles[n].delete(); } } } if (expiredCount > 0) { Logger.log(Logger.DEBUG, Launcher.RESOURCES, "WebAppConfig.InvalidatedSessions", expiredCount + ""); } } /** * Serialization implementation. This makes sure to only serialize the parts * we want to send to another server. * * @param out * The stream to write the contents to * @throws IOException */ private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeUTF(sessionId); out.writeLong(createTime); out.writeLong(lastAccessedTime); out.writeInt(maxInactivePeriod); out.writeBoolean(isNew); out.writeBoolean(distributable); // Write the map, but first remove non-serializables Map copy = new HashMap(sessionData); Set keys = new HashSet(copy.keySet()); for (Iterator i = keys.iterator(); i.hasNext();) { String key = (String) i.next(); if (!(copy.get(key) instanceof Serializable)) { Logger.log(Logger.WARNING, Launcher.RESOURCES, "WinstoneSession.SkippingNonSerializable", new String[] { key, copy.get(key).getClass().getName() }); } copy.remove(key); } out.writeInt(copy.size()); for (Iterator i = copy.keySet().iterator(); i.hasNext();) { String key = (String) i.next(); out.writeUTF(key); out.writeObject(copy.get(key)); } } /** * Deserialization implementation * * @param in * The source of stream data * @throws IOException * @throws ClassNotFoundException */ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { this.sessionId = in.readUTF(); this.createTime = in.readLong(); this.lastAccessedTime = in.readLong(); this.maxInactivePeriod = in.readInt(); this.isNew = in.readBoolean(); this.distributable = in.readBoolean(); // Read the map this.sessionData = new Hashtable(); this.requestsUsingMe = new HashSet(); int entryCount = in.readInt(); for (int n = 0; n < entryCount; n++) { String key = in.readUTF(); Object variable = in.readObject(); this.sessionData.put(key, variable); } this.sessionMonitor = new Boolean(true); } /** * @deprecated */ public Object getValue(String name) { return getAttribute(name); } /** * @deprecated */ public void putValue(String name, Object value) { setAttribute(name, value); } /** * @deprecated */ public void removeValue(String name) { removeAttribute(name); } /** * @deprecated */ public String[] getValueNames() { return (String[]) this.sessionData.keySet().toArray(new String[0]); } /** * @deprecated */ public javax.servlet.http.HttpSessionContext getSessionContext() { return null; } // deprecated }