You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2009/07/22 21:02:50 UTC

svn commit: r796832 - in /incubator/shiro/trunk: core/src/main/java/org/apache/shiro/mgt/ core/src/main/java/org/apache/shiro/session/ core/src/main/java/org/apache/shiro/session/mgt/ core/src/main/java/org/apache/shiro/session/mgt/eis/ core/src/main/j...

Author: lhazlewood
Date: Wed Jul 22 19:02:49 2009
New Revision: 796832

URL: http://svn.apache.org/viewvc?rev=796832&view=rev
Log:
- SHIRO-53 - enabled auto-deletion of Session objects after invalidation to prevent orphans.  Added DefaultSessionManagerTest#testSessionDeleteOnExpiration() testcase for verification.
- Removed auto-deletion of cached objects from CachingSessionDAO - this was preventing the SessionManager from performing appropriate cleanup/notification logic necessary for any invalid sessions held by the cache (this was preventing SessionListeners from receiving onExpiration and onStop callbacks)
- Added proper toString, equals and hashCode implementations to SimpleSession

Modified:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java
    incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java
    incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java Wed Jul 22 19:02:49 2009
@@ -509,7 +509,7 @@
      * @see org.apache.shiro.authz.HostUnauthorizedException
      * @since 1.0
      */
-    private Subject getSubjectBySessionId(Serializable sessionId) throws InvalidSessionException, AuthorizationException {
+    protected Subject getSubjectBySessionId(Serializable sessionId) throws InvalidSessionException, AuthorizationException {
         Session session = getSession(sessionId);
 
         Map<String, Object> context = new HashMap<String, Object>(1);

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java Wed Jul 22 19:02:49 2009
@@ -128,9 +128,7 @@
      */
     Subject getSubject(Map context);
 
-    /*Subject getSubject(Map initData);
+    //Subject getSubjectBySessionId(Serializable sessionId);
 
-    Subject getSubjectBySessionId(Serializable sessionId);
-
-    Subject getSubject(PrincipalCollection principals);*/
+    //Subject getSubject(PrincipalCollection principals);
 }

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java Wed Jul 22 19:02:49 2009
@@ -71,7 +71,9 @@
      * @param sessionId the session id given that is unknown to the system.
      */
     public UnknownSessionException(Serializable sessionId) {
-        super(sessionId);
+        super("Unable to locate session with id [" + sessionId + "] either because it is an invalid id " +
+                "or the session has been deleted due to invalidation (stopped, logged out, or expired).",
+                sessionId);
     }
 
     /**

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java Wed Jul 22 19:02:49 2009
@@ -143,7 +143,7 @@
      * <em>after</em> the {@link #onStart(org.apache.shiro.session.Session)} method is called.
      *
      * @param session the session that has just started that will be delivered to any
-     * {@link #setSessionListeners(java.util.Collection) registered} session listeners.
+     *                {@link #setSessionListeners(java.util.Collection) registered} session listeners.
      * @see SessionListener#onStart(org.apache.shiro.session.Session)
      */
     protected void notifyStart(Session session) {
@@ -206,6 +206,10 @@
         session.stop();
         onStop(session);
         notifyStop(session);
+        afterStopped(session);
+    }
+
+    protected void afterStopped(Session session) {
     }
 
     public Collection<Object> getAttributeKeys(Serializable sessionId) {
@@ -236,6 +240,9 @@
     }
 
     protected Session getSession(Serializable sessionId) throws InvalidSessionException {
+        if (sessionId == null) {
+            throw new IllegalArgumentException("sessionId parameter cannot be null.");
+        }
         Session session = doGetSession(sessionId);
         if (session == null) {
             String msg = "There is no session with id [" + sessionId + "]";
@@ -272,10 +279,6 @@
         onChange(session);
     }
 
-    protected void onExpiration(Session session) {
-        onChange(session);
-    }
-
     protected void onChange(Session s) {
     }
 

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java Wed Jul 22 19:02:49 2009
@@ -19,10 +19,7 @@
 package org.apache.shiro.session.mgt;
 
 import org.apache.shiro.authz.AuthorizationException;
-import org.apache.shiro.session.ExpiredSessionException;
-import org.apache.shiro.session.InvalidSessionException;
-import org.apache.shiro.session.ReplacedSessionException;
-import org.apache.shiro.session.Session;
+import org.apache.shiro.session.*;
 import org.apache.shiro.util.Destroyable;
 import org.apache.shiro.util.LifecycleUtils;
 import org.apache.shiro.util.ThreadContext;
@@ -152,7 +149,22 @@
         this.autoCreateWhenInvalid = autoCreateWhenInvalid;
     }
 
-    protected final Session doGetSession(Serializable sessionId) throws InvalidSessionException {
+    private InetAddress getHostAddressFallback(Session s) {
+        InetAddress inet = s.getHostAddress();
+        if (inet == null) {
+            //fallback to thread local just in case:
+            inet = ThreadContext.getInetAddress();
+        }
+        return inet;
+    }
+
+    private static void assertNotNull(Session session, Serializable sessionId) throws UnknownSessionException {
+        if (session == null) {
+            throw new UnknownSessionException(sessionId);
+        }
+    }
+
+    protected final Session doGetSession(final Serializable sessionId) throws InvalidSessionException {
         enableSessionValidationIfNecessary();
 
         if (log.isTraceEnabled()) {
@@ -161,35 +173,31 @@
         InetAddress hostAddress = null;
         try {
             Session s = retrieveSession(sessionId);
-            //save the host address in case the session will be invalidated.  We want to retain it for the
-            //replacement session:
-            hostAddress = s.getHostAddress();
+            assertNotNull(s, sessionId);
+            // Save the host address in case the session will be invalidated.
+            // We want to retain it in case it is needed for a replacement session
+            hostAddress = getHostAddressFallback(s);
             validate(s);
             return s;
         } catch (InvalidSessionException ise) {
-            if (isAutoCreateWhenInvalid()) {
-                if (hostAddress == null) {
-                    //try the threadContext as a last resort:
-                    hostAddress = ThreadContext.getInetAddress();
-                }
-                Serializable newId = start(hostAddress);
-                String msg = "Session with id [" + sessionId + "] is invalid.  The SessionManager " +
-                        "has been configured to automatically re-create sessions upon invalidation.  Returnining " +
-                        "new session id [" + newId + "] with exception so the caller may react accordingly.";
-                throw new ReplacedSessionException(msg, ise, sessionId, newId);
-            } else {
-                //propagate original exception:
+            if (!isAutoCreateWhenInvalid()) {
                 throw ise;
             }
+            //otherwise auto-create a new session and indicate via a ReplacedSessionException
+            Serializable newId = start(hostAddress);
+            String msg = "Session with id [" + sessionId + "] is invalid.  The SessionManager " +
+                    "has been configured to automatically re-create sessions upon invalidation.  Returnining " +
+                    "new session id [" + newId + "] with exception so the caller may react accordingly.";
+            throw new ReplacedSessionException(msg, ise, sessionId, newId);
         }
     }
 
     /**
      * Looks up a session from the underlying data store based on the specified {@code sessionId}.
      *
-     * @param sessionId
-     * @return
-     * @throws InvalidSessionException
+     * @param sessionId the id of the session to retrieve from the data store
+     * @return the session identified by {@code sessionId}.
+     * @throws InvalidSessionException if there is no session identified by {@code sessionId}.
      */
     protected abstract Session retrieveSession(Serializable sessionId) throws InvalidSessionException;
 
@@ -203,12 +211,36 @@
     protected void validate(Session session) throws InvalidSessionException {
         try {
             doValidate(session);
-        } catch (ExpiredSessionException ese) {
-            onExpiration(session);
-            notifyExpiration(session);
-            //propagate to caller:
-            throw ese;
+        } catch (InvalidSessionException ise) {
+            onInvalidation(session, ise);
+            throw ise;
+        }
+    }
+
+    protected void onInvalidation(Session session, InvalidSessionException ise) {
+        if (ise instanceof ExpiredSessionException) {
+            onExpiration(session, (ExpiredSessionException) ise);
+            return;
         }
+        if (log.isDebugEnabled()) {
+            log.debug("Session with id [" + session.getId() + "] is invalid.");
+        }
+        onStop(session);
+        notifyStop(session);
+        afterStopped(session);
+    }
+
+    protected void onExpiration(Session s, ExpiredSessionException ese) {
+        onExpiration(s);
+        notifyExpiration(s);
+        afterExpired(s);
+    }
+
+    protected void onExpiration(Session session) {
+        onChange(session);
+    }
+
+    protected void afterExpired(Session session) {
     }
 
     protected void doValidate(Session session) throws InvalidSessionException {

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java Wed Jul 22 19:02:49 2009
@@ -50,7 +50,9 @@
 
     private SessionFactory sessionFactory;
 
-    protected SessionDAO sessionDAO;
+    protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?
+
+    private boolean deleteInvalidSessions = true;
 
     public DefaultSessionManager() {
         this.sessionFactory = new SimpleSessionFactory();
@@ -87,6 +89,44 @@
         this.sessionFactory = sessionFactory;
     }
 
+    /**
+     * Returns {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
+     * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.  The
+     * default is {@code true} to ensure no orphans exist in the underlying data store.
+     * <h4>Usage</h4>
+     * It is ok to set this to {@code false} <b><em>ONLY</em></b> if you have some other process that you manage yourself
+     * that periodically deletes invalid sessions from the backing data store over time, such as via a Quartz or Cron
+     * job.  If you do not do this, the invalid sessions will become 'orphans' and fill up the data store over time.
+     * <p/>
+     * This property is provided because some systems need the ability to perform querying/reporting against sessions in
+     * the data store, even after they have stopped or expired.  Setting this attribute to {@code false} will allow
+     * such querying, but with the caveat that the application developer/configurer deletes the sessions themselves by
+     * some other means (cron, quartz, etc).
+     *
+     * @return {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
+     *         {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.
+     * @since 1.0
+     */
+    public boolean isDeleteInvalidSessions() {
+        return deleteInvalidSessions;
+    }
+
+    /**
+     * Sets whether or not sessions should be automatically deleted after they are discovered to be invalid.  Default
+     * value is {@code true} to ensure no orphans will exist in the underlying data store.
+     * <h4>WARNING</h4>
+     * Only set this value to {@code false} if you are manually going to delete sessions yourself by some process
+     * (quartz, cron, etc) external to Shiro's control.  See the
+     * {@link #isDeleteInvalidSessions() isDeleteInvalidSessions()} JavaDoc for more.
+     *
+     * @param deleteInvalidSessions whether or not sessions should be automatically deleted after they are discovered
+     *                              to be invalid.
+     * @since 1.0
+     */
+    public void setDeleteInvalidSessions(boolean deleteInvalidSessions) {
+        this.deleteInvalidSessions = deleteInvalidSessions;
+    }
+
     public void setCacheManager(CacheManager cacheManager) {
         if (this.sessionDAO instanceof CacheManagerAware) {
             ((CacheManagerAware) this.sessionDAO).setCacheManager(cacheManager);
@@ -129,6 +169,13 @@
         onChange(session);
     }
 
+    @Override
+    protected void afterStopped(Session session) {
+        if (isDeleteInvalidSessions()) {
+            delete(session);
+        }
+    }
+
     protected void onExpiration(Session session) {
         if (session instanceof SimpleSession) {
             ((SimpleSession) session).setExpired(true);
@@ -136,6 +183,13 @@
         onChange(session);
     }
 
+    @Override
+    protected void afterExpired(Session session) {
+        if (isDeleteInvalidSessions()) {
+            delete(session);
+        }
+    }
+
     protected void onChange(Session session) {
         sessionDAO.update(session);
     }
@@ -151,6 +205,10 @@
         return sessionDAO.readSession(sessionId);
     }
 
+    protected void delete(Session session) {
+        sessionDAO.delete(session);
+    }
+
     protected Collection<Session> getActiveSessions() {
         Collection<Session> active = sessionDAO.getActiveSessions();
         return active != null ? active : CollectionUtils.emptyCollection(Session.class);

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java Wed Jul 22 19:02:49 2009
@@ -86,7 +86,7 @@
     /**
      * Returns the time the session was stopped, or <tt>null</tt> if the session is still active.
      * <p/>
-     * <p>A session may become stopped under a number of conditions:
+     * A session may become stopped under a number of conditions:
      * <ul>
      * <li>If the user logs out of the system, their current session is terminated (released).</li>
      * <li>If the session expires</li>
@@ -94,9 +94,8 @@
      * <li>If there is an internal system error and the session state can no longer accurately
      * reflect the user's behavior, such in the case of a system crash</li>
      * </ul>
-     * </p>
      * <p/>
-     * <p>Once stopped, a session may no longer be used.  It is locked from all further activity.
+     * Once stopped, a session may no longer be used.  It is locked from all further activity.
      *
      * @return The time the session was stopped, or <tt>null</tt> if the session is still
      *         active.
@@ -303,4 +302,92 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the specified argument is an {@code instanceof} {@code SimpleSession} and both
+     * {@link #getId() id}s are equal.  If the argument is a {@code SimpleSession} and either 'this' or the argument
+     * does not yet have an ID assigned, the value of {@link #onEquals(SimpleSession) onEquals} is returned, which
+     * does a necessary attribute-based comparison when IDs are not available.
+     * <p/>
+     * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
+     * avoid the more expensive attributes-based comparison.
+     *
+     * @param obj the object to compare with this one for equality.
+     * @return {@code true} if this object is equivalent to the specified argument, {@code false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof SimpleSession) {
+            SimpleSession other = (SimpleSession) obj;
+            Serializable thisId = getId();
+            Serializable otherId = other.getId();
+            if (thisId != null && otherId != null) {
+                return thisId.equals(otherId);
+            } else {
+                //fall back to an attribute based comparison:
+                return onEquals(other);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Provides an attribute-based comparison (no ID comparison) - incurred <em>only</em> when 'this' or the
+     * session object being compared for equality do not have a session id.
+     *
+     * @param ss the SimpleSession instance to compare for equality.
+     * @return true if all the attributes, except the id, are equal to this object's attributes.
+     * @since 1.0
+     */
+    protected boolean onEquals(SimpleSession ss) {
+        return (getStartTimestamp() != null ? getStartTimestamp().equals(ss.getStartTimestamp()) : ss.getStartTimestamp() == null) &&
+                (getStopTimestamp() != null ? getStopTimestamp().equals(ss.getStopTimestamp()) : ss.getStopTimestamp() == null) &&
+                (getLastAccessTime() != null ? getLastAccessTime().equals(ss.getLastAccessTime()) : ss.getLastAccessTime() == null) &&
+                (getTimeout() == ss.getTimeout()) &&
+                (isExpired() == ss.isExpired()) &&
+                (getHostAddress() != null ? getHostAddress().equals(ss.getHostAddress()) : ss.getHostAddress() == null) &&
+                (getAttributes() != null ? getAttributes().equals(ss.getAttributes()) : ss.getAttributes() == null);
+    }
+
+    /**
+     * Returns the hashCode.  If the {@link #getId() id} is not {@code null}, its hashcode is returned immediately.
+     * If it is {@code null}, an attributes-based hashCode will be calculated and returned.
+     * <p/>
+     * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
+     * avoid the more expensive attributes-based calculation.
+     *
+     * @return this object's hashCode
+     * @since 1.0
+     */
+    @Override
+    public int hashCode() {
+        if (getId() != null) {
+            return getId().hashCode();
+        }
+        int hashCode = getStartTimestamp() != null ? getStartTimestamp().hashCode() : 0;
+        hashCode = 31 * hashCode + (getStopTimestamp() != null ? getStopTimestamp().hashCode() : 0);
+        hashCode = 31 * hashCode + (getLastAccessTime() != null ? getLastAccessTime().hashCode() : 0);
+        hashCode = 31 * hashCode + Long.valueOf(Math.max(getTimeout(), 0)).hashCode();
+        hashCode = 31 * hashCode + Boolean.valueOf(isExpired()).hashCode();
+        hashCode = 31 * hashCode + (getHostAddress() != null ? getHostAddress().hashCode() : 0);
+        hashCode = 31 * hashCode + (getAttributes() != null ? getAttributes().hashCode() : 0);
+        return hashCode;
+    }
+
+    /**
+     * Returns the string representation of this SimpleSession, equal to
+     * <code>getClass().getName() + &quot;,id=&quot; + getId()</code>.
+     *
+     * @return the string representation of this SimpleSession, equal to
+     *         <code>getClass().getName() + &quot;,id=&quot; + getId()</code>.
+     * @since 1.0
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getName()).append(",id=").append(getId());
+        return sb.toString();
+    }
 }

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java Wed Jul 22 19:02:49 2009
@@ -18,22 +18,21 @@
  */
 package org.apache.shiro.session.mgt.eis;
 
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Collections;
-
 import org.apache.shiro.cache.Cache;
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.cache.CacheManagerAware;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.UnknownSessionException;
-import org.apache.shiro.session.mgt.ValidatingSession;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
 
 
 /**
  * An CachingSessionDAO is a SessionDAO that provides a transparent caching layer between the components that
  * use it and the underlying EIS (Enterprise Information System) for enhanced performance.
- *
+ * <p/>
  * <p>This implementation caches all active sessions in a cache created by a
  * {@link org.apache.shiro.cache.CacheManager}.  All <tt>SessionDAO</tt> methods are implemented by this class to employ
  * caching behavior and delegates the actual EIS operations to respective do* methods to be implemented by
@@ -93,6 +92,7 @@
     /**
      * Returns the name of the actives sessions cache to be returned by the <code>CacheManager</code>.  Unless
      * overridden by {@link #setActiveSessionsCacheName(String)}, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
+     *
      * @return the name of the active sessions cache.
      */
     public String getActiveSessionsCacheName() {
@@ -102,7 +102,8 @@
     /**
      * Sets the name of the active sessions cache to be returned by the <code>CacheManager</code>.  Defaults to
      * {@link #ACTIVE_SESSION_CACHE_NAME}.
-     * @param activeSessionsCacheName the name of the active sessions cache to be returned by the <code>CacheManager</code>. 
+     *
+     * @param activeSessionsCacheName the name of the active sessions cache to be returned by the <code>CacheManager</code>.
      */
     public void setActiveSessionsCacheName(String activeSessionsCacheName) {
         this.activeSessionsCacheName = activeSessionsCacheName;
@@ -110,6 +111,7 @@
 
     /**
      * Returns the cache instance to use for storing active sessions.
+     *
      * @return the cache instance to use for storing active sessions.
      */
     public Cache getActiveSessionsCache() {
@@ -122,7 +124,7 @@
      * <p/>
      * Note that this method will only return a non-null value code if the <code>CacheManager</code> has been set.  If
      * not set, there will be no cache.
-     * 
+     *
      * @return the active sessions cache instance.
      */
     protected Cache getActiveSessionsCacheLazy() {
@@ -134,6 +136,7 @@
 
     /**
      * Sets the cache instance to use for storing active sessions.
+     *
      * @param cache the cache instance to use for storing active sessions.
      */
     public void setActiveSessionsCache(Cache cache) {
@@ -146,8 +149,9 @@
      * cache returned is that resulting from the following call:
      * <pre>       String name = {@link #getActiveSessionsCacheName() getActiveSessionsCacheName()};
      * cacheManager.getCache(name);</pre>
+     *
      * @return a cache instance used to store active sessions, or <em>null</code> if the <code>CacheManager</code> has
-     * not been set.
+     *         not been set.
      */
     protected Cache createActiveSessionsCache() {
         Cache cache = null;
@@ -168,7 +172,7 @@
     public Serializable create(Session session) {
         Serializable sessionId = doCreate(session);
         verifySessionId(sessionId);
-        cacheValidSession(session, sessionId);
+        cache(session, sessionId);
         return sessionId;
     }
 
@@ -178,7 +182,7 @@
      *
      * @param sessionId the id of the cached session to acquire.
      * @return the cached session with the corresponding <code>sessionId</code>, or <code>null</code> if the session
-     * does not exist or is not cached.
+     *         does not exist or is not cached.
      */
     protected Session getCachedSession(Serializable sessionId) {
         Session cached = null;
@@ -194,8 +198,9 @@
     /**
      * Returns the Session with the specified id from the specified cache.  This method simply calls
      * <code>cache.get(sessionId)</code> and can be overridden by subclasses for custom acquisition behavior.
+     *
      * @param sessionId the id of the session to acquire.
-     * @param cache the cache to acquire the session from
+     * @param cache     the cache to acquire the session from
      * @return the cached session, or <code>null</code> if the session wasn't in the cache.
      */
     protected Session getCachedSession(Serializable sessionId, Cache cache) {
@@ -203,37 +208,30 @@
     }
 
     /**
-     * Caches the specified session under the key <code>sessionId</code>.  If the Session is an instance of
-     * {@link org.apache.shiro.session.mgt.ValidatingSession ValidatingSession}, it will only be cached if the
-     * session is {@link org.apache.shiro.session.mgt.ValidatingSession#isValid() valid}.
+     * Caches the specified session under the cache entry key of {@code sessionId}.
      *
-     * @param session the session to cache
-     * @param sessionId the id of the session, to be used as the cache key.
+     * @param session   the session to cache
+     * @param sessionId the session id, to be used as the cache entry key.
+     * @since 1.0
      */
-    protected void cacheValidSession(Session session, Serializable sessionId) {
+    protected void cache(Session session, Serializable sessionId) {
         if (session == null || sessionId == null) {
             return;
         }
-
         Cache cache = getActiveSessionsCacheLazy();
         if (cache == null) {
             return;
         }
-
-        if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
-            uncache(session);
-        } else {
-            cache(session, sessionId, cache);
-        }
+        cache(session, sessionId, cache);
     }
 
     /**
      * Caches the specified session in the given cache under the key of <code>sessionId</code>.  This implementation
      * simply calls <code>cache.put(sessionId, session)</code> and can be overridden for custom behavior.
-     * 
-     * @param session the session to cache
+     *
+     * @param session   the session to cache
      * @param sessionId the id of the session, expected to be the cache key.
-     * @param cache the cache to store the session
+     * @param cache     the cache to store the session
      */
     protected void cache(Session session, Serializable sessionId, Cache cache) {
         cache.put(sessionId, session);
@@ -250,22 +248,6 @@
             String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
             throw new IllegalStateException(msg);
         }
-        ensureUncached(sessionId);
-    }
-
-    /**
-     * Ensures that there is no cache entry already in place for a session with id of <tt>sessionId</tt>.  Used by
-     * the {@link #verifySessionId} implementation.
-     *
-     * @param sessionId the session id to check for non-existence in the cache.
-     */
-    protected void ensureUncached(Serializable sessionId) {
-        Cache cache = getActiveSessionsCacheLazy();
-        if (cache != null && cache.get(sessionId) != null) {
-            String msg = "There is an existing session already created with session id [" +
-                    sessionId + "].  Session ID's must be unique.";
-            throw new IllegalArgumentException(msg);
-        }
     }
 
     /**
@@ -279,7 +261,7 @@
 
     /**
      * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt>.
-     *
+     * <p/>
      * <p>Upon receiving the Session object from the subclass's {@link #doReadSession} implementation, it will be
      * cached first and then returned to the caller.
      *
@@ -293,9 +275,6 @@
 
         if (s == null) {
             s = doReadSession(sessionId);
-            if (s != null) {
-                cacheValidSession(s, sessionId);
-            }
         }
 
         if (s == null) {
@@ -314,13 +293,13 @@
 
     /**
      * Updates the state of the given session to the EIS.
-     *
+     * <p/>
      * <p>If the specified session was previously cached, and the session is now invalid,
      * it will be removed from the cache.
-     *
+     * <p/>
      * <p>If the specified session is not stopped or expired, and was not yet in the cache, it will be added to the
      * cache.
-     *
+     * <p/>
      * <p>Finally, this method calls {@link #doUpdate} for the subclass to actually push the object state to the EIS.
      *
      * @param session the session object to update in the EIS.
@@ -329,7 +308,7 @@
      */
     public void update(Session session) throws UnknownSessionException {
         doUpdate(session);
-        cacheValidSession(session, session.getId());
+        cache(session, session.getId());
     }
 
     /**
@@ -346,8 +325,8 @@
      * @param session the session to remove from caches and permanently delete from the EIS.
      */
     public void delete(Session session) {
-        doDelete(session);
         uncache(session);
+        doDelete(session);
     }
 
     /**
@@ -378,7 +357,7 @@
 
     /**
      * Returns all active sessions in the system.
-     *
+     * <p/>
      * <p>This implementation merely returns the sessions found in the activeSessions cache.  Subclass implementations
      * may wish to override this method to retrieve them in a different way, perhaps by an RDBMS query or by other
      * means.

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java Wed Jul 22 19:02:49 2009
@@ -18,32 +18,31 @@
  */
 package org.apache.shiro.session.mgt.eis;
 
-import java.io.Serializable;
-import java.util.Random;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.apache.shiro.cache.HashtableCacheManager;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.SimpleSession;
 import org.apache.shiro.util.JavaEnvironment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Random;
 
 
 /**
  * Simple memory-based implementation of the SessionDAO that relies on its configured
  * {@link #setCacheManager CacheManager} for Session caching and in-memory persistence.
- *
+ * <p/>
  * <p><b>PLEASE NOTE</b> the default CacheManager internal to this implementation is a
  * {@link org.apache.shiro.cache.HashtableCacheManager HashtableCacheManager}, which IS NOT RECOMMENDED for production environments.
- *
+ * <p/>
  * <p>If you
  * want to use the MemorySessionDAO in production environments, such as those that require session data to be
  * recoverable in case of a server restart, you should do one of two things (or both):
- *
+ * <p/>
  * <ul>
  * <li>Configure it with a production-quality CacheManager. The
- * {@link org.apache.shiro.cache.ehcache.EhCacheManager EhCacheManager} is one such implementation.  It is not used by default
+ * {@code org.apache.shiro.cache.ehcache.EhCacheManager} is one such implementation.  It is not used by default
  * to prevent a forced runtime dependency on ehcache.jar that may not be required in many environments)</li><br/>
  * <li>If you need session information beyond their transient start/stop lifetimes, you should subclass this one and
  * override the <tt>do*</tt> methods to perform CRUD operations using an EIS-tier API (e.g. Hibernate/JPA/JCR/etc).

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/subject/DelegatingSubject.java Wed Jul 22 19:02:49 2009
@@ -180,14 +180,14 @@
 
     protected void assertAuthzCheckPossible() throws AuthorizationException {
         if (!hasPrincipals()) {
-            String msg = "Identity principals are not associated with this Subject instance - " +
+            String msg = "This subject is anonymous - it does not have any identifying principals associated, and " +
                     "authorization operations require an identity to check against.  A Subject instance will " +
                     "acquire these identifying principals automatically after a successful login is performed " +
                     "be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " +
-                    "functionality is enabled.  This exception can also occur when the current Subject has logged out, " +
-                    "which relinquishes its identity and essentially makes it anonymous again.  " +
-                    "Because an identity is currently not known due to any of these conditions, " +
-                    "authorization is denied.";
+                    "functionality is enabled by the SecurityManager.  This exception can also occur when a " +
+                    "previously logged-in Subject has logged out, which relinquishes its identity and essentially " +
+                    "makes it anonymous again.  Because an identity is currently not known due to any of these " +
+                    "conditions, authorization is denied.";
             throw new UnauthenticatedException(msg);
         }
     }
@@ -294,7 +294,7 @@
             this.session = null;
             this.principals = null;
             this.authenticated = false;
-            //Don't set securityManager to null here - the Subject can be continued to be
+            //Don't set securityManager to null here - the Subject can still be
             //used, it is just considered anonymous at this point.  The SecurityManager instance is
             //necessary if the subject would log in again or acquire a new session.  This is in response to
             //https://issues.apache.org/jira/browse/JSEC-22

Modified: incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java (original)
+++ incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java Wed Jul 22 19:02:49 2009
@@ -18,15 +18,20 @@
  */
 package org.apache.shiro.session.mgt;
 
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
 import org.apache.shiro.util.ThreadContext;
+import static org.easymock.EasyMock.*;
 import org.junit.After;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.Serializable;
 import java.net.InetAddress;
+import java.util.Map;
+import java.util.UUID;
 
 /**
  * Unit test for the {@link DefaultSessionManager DefaultSessionManager} implementation.
@@ -63,4 +68,59 @@
         sleep(150);
         assertFalse(sm.isValid(sessionId));
     }
+
+    @Test
+    public void testSessionDeleteOnExpiration() {
+
+        sm.setAutoCreateWhenInvalid(false);
+        sm.setGlobalSessionTimeout(100);
+
+        SessionDAO sessionDAO = createMock(SessionDAO.class);
+        sm.setSessionDAO(sessionDAO);
+
+        String sessionId1 = UUID.randomUUID().toString();
+        final SimpleSession session1 = new SimpleSession();
+        session1.setId(sessionId1);
+        System.out.println("Session id 1: " + sessionId1);
+
+        final Session[] activeSession = new SimpleSession[]{session1};
+        sm.setSessionFactory(new SessionFactory() {
+            public Session createSession(Map initData) {
+                return activeSession[0];
+            }
+        });
+
+        expect(sessionDAO.create(eq(session1))).andReturn(sessionId1);
+        sessionDAO.update(eq(session1));
+        expectLastCall().anyTimes();
+        replay(sessionDAO);
+        Serializable id = sm.start((InetAddress) null);
+        assertNotNull(id);
+        verify(sessionDAO);
+        reset(sessionDAO);
+
+        expect(sessionDAO.readSession(sessionId1)).andReturn(session1).anyTimes();
+        sessionDAO.update(eq(session1));
+        replay(sessionDAO);
+        sm.setTimeout(sessionId1, 1);
+        verify(sessionDAO);
+        reset(sessionDAO);
+
+        sleep(20);
+
+        expect(sessionDAO.readSession(sessionId1)).andReturn(session1);
+        sessionDAO.update(eq(session1)); //update's the stop timestamp
+        sessionDAO.delete(session1);
+        replay(sessionDAO);
+
+        //Try to access the same session, but it should throw an UnknownSessionException due to timeout:
+        try {
+            sm.getTimeout(sessionId1);
+            fail("Session with id [" + sessionId1 + "] should have expired due to timeout.");
+        } catch (ExpiredSessionException expected) {
+            //expected
+        }
+
+        verify(sessionDAO); //verify that the delete call was actually made on the DAO
+    }
 }

Modified: incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java (original)
+++ incubator/shiro/trunk/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java Wed Jul 22 19:02:49 2009
@@ -63,7 +63,7 @@
         Serializable origId = session.getId();
         assertEquals(session.getTimeout(), AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
         session.setTimeout(100);
-        assertEquals(100, session.getTimeout());
+        assertEquals(session.getTimeout(), 100);
         sleep(150);
         //now the underlying session should have been expired and a new one replaced by default.
         //so ensure the replaced session has the default session timeout:

Modified: incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java?rev=796832&r1=796831&r2=796832&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java (original)
+++ incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DefaultWebSecurityManagerTest.java Wed Jul 22 19:02:49 2009
@@ -18,19 +18,15 @@
  */
 package org.apache.shiro.web;
 
-import org.apache.shiro.util.ThreadContext;
 import org.apache.shiro.session.Session;
-import org.apache.shiro.session.mgt.AbstractSessionManager;
 import org.apache.shiro.subject.Subject;
-import org.apache.shiro.authc.UsernamePasswordToken;
-import org.apache.shiro.authc.AuthenticationInfo;
-import org.apache.shiro.authc.SimpleAuthenticationInfo;
-import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.util.ThreadContext;
+import static org.easymock.EasyMock.*;
 import org.junit.After;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import org.junit.Before;
 import org.junit.Test;
-import static org.junit.Assert.*;
-import static org.easymock.EasyMock.*;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -96,8 +92,6 @@
         //so ensure the replaced session has the default session timeout:
         assertEquals(session.getTimeout(), globalTimeout);
         assertFalse(origId.equals(session.getId())); //new ID would have been generated
-
-        //verify(mockRequest);
     }
 
 }