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 2012/06/18 04:01:12 UTC

svn commit: r1351193 [1/2] - in /shiro/branches/SHIRO-317b: core/src/main/java/org/apache/shiro/event/ core/src/main/java/org/apache/shiro/session/event/ core/src/main/java/org/apache/shiro/session/mgt/ core/src/main/java/org/apache/shiro/session/mgt/e...

Author: lhazlewood
Date: Mon Jun 18 02:01:11 2012
New Revision: 1351193

URL: http://svn.apache.org/viewvc?rev=1351193&view=rev
Log:
- Added new first-level-caching Web SessionManager implementation
- Condensed core SessionManager hierarchy to a single more easily understood StandardSessionManager implementation.
 - Added event infrastructure mechanism for loose coupling.  
 - Updated ID generation to use a Java Pattern object for slightly better performance, additionally using all uppercase characters for easier visual log file scanning.  
 - Created new RequestIdGenerator for request-specific globally unique IDs.  
 - Introduced new session AccessTimestampEvaluator to allow end-users to configure which requests update a session's lastAccessTimestamp.

Added:
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Publisher.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/ShiroEvent.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubjectEvent.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Subscriber.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubscriberRegistry.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SynchronousEventBus.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/InvalidSessionEvent.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/SessionEvent.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StartedSessionEvent.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StoppedSessionEvent.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/StandardSessionManager.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/util/Assert.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/BeginServletRequestEvent.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/EndServletRequestEvent.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/ServletRequestEvent.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/AccessTimestampEvaluator.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/RequestIdGenerator.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/SpecCompliantAccessTimestampEvaluator.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/StandardWebSessionManager.java
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/UuidRequestIdGenerator.java
Modified:
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java
    shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
    shiro/branches/SHIRO-317b/samples/guice/   (props changed)
    shiro/branches/SHIRO-317b/samples/quickstart-guice/   (props changed)
    shiro/branches/SHIRO-317b/samples/web/pom.xml
    shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Publisher.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Publisher.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Publisher.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Publisher.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,9 @@
+package org.apache.shiro.event;
+
+/**
+ * @since 1.3
+ */
+public interface Publisher {
+
+    void publish(Object event);
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/ShiroEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/ShiroEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/ShiroEvent.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/ShiroEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,21 @@
+package org.apache.shiro.event;
+
+import java.util.Date;
+import java.util.EventObject;
+
+/**
+ * @since 1.3
+ */
+public class ShiroEvent extends EventObject {
+
+    private final long timestamp; //millis since Epoch (UTC time zone).
+
+    public ShiroEvent(Object source) {
+        super(source);
+        this.timestamp = new Date().getTime();
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubjectEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubjectEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubjectEvent.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubjectEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,20 @@
+package org.apache.shiro.event;
+
+import org.apache.shiro.subject.Subject;
+
+/**
+ * @since 1.3
+ */
+public class SubjectEvent extends ShiroEvent {
+
+    private final Subject subject;
+
+    public SubjectEvent(Subject subject) {
+        super(subject);
+        this.subject = subject;
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Subscriber.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Subscriber.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Subscriber.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/Subscriber.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,9 @@
+package org.apache.shiro.event;
+
+/**
+ * @since 1.3
+ */
+public interface Subscriber {
+
+    void onEvent(Object event);
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubscriberRegistry.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubscriberRegistry.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubscriberRegistry.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SubscriberRegistry.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,15 @@
+package org.apache.shiro.event;
+
+/**
+ * @since 1.3
+ */
+public interface SubscriberRegistry {
+
+    void subscribe(Subscriber subscriber);
+
+    void subscribe(Subscriber subscriber, Class... messageTypes);
+
+    void unsubscribe(Subscriber subscriber);
+
+    void unsubscribe(Subscriber subscriber, Class... messageTypes);
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SynchronousEventBus.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SynchronousEventBus.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SynchronousEventBus.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/event/SynchronousEventBus.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,255 @@
+package org.apache.shiro.event;
+
+import org.apache.shiro.util.Assert;
+
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * @since 1.3
+ */
+public class SynchronousEventBus implements Publisher, SubscriberRegistry {
+
+    private final InternalRegistry registry;
+
+    public SynchronousEventBus() {
+        this.registry = new InternalRegistry();
+    }
+
+    public void publish(Object event) {
+        if (event == null) {
+            return;
+        }
+        Class eventClass = event.getClass();
+        Set<Class> keys = registry.keySet();
+        for( Class clazz : keys) {
+            if (clazz.isAssignableFrom(eventClass)) {
+                List<Subscriber> subscribers = registry.get(clazz);
+                if (subscribers != null) {
+                    for (Subscriber subscriber : subscribers) {
+                        subscriber.onEvent(event);
+                    }
+                }
+            }
+        }
+    }
+
+    public void subscribe(Subscriber subscriber) {
+        subscribe(subscriber, Object.class);
+    }
+
+    public void subscribe(Subscriber subscriber, Class... types) {
+        Assert.notNull(subscriber, "Subscriber argument cannot be null.");
+        types = (types != null && types.length > 0) ? types : new Class[]{Object.class};
+
+        for(Class clazz : types) {
+            this.registry.subscribe(clazz, subscriber);
+        }
+    }
+
+    public void unsubscribe(Subscriber subscriber) {
+        unsubscribe(subscriber, (Class[])null);
+    }
+
+    public void unsubscribe(Subscriber subscriber, Class... types) {
+        if (subscriber == null) {
+            return;
+        }
+        if (types == null) {
+            this.registry.unsubscribe(subscriber);
+        } else {
+            for (Class clazz : types) {
+                this.registry.unsubscribe(clazz, subscriber);
+            }
+        }
+    }
+
+    private static class SubscribedClassComparator implements Comparator<Class> {
+
+        public int compare(Class a, Class b) {
+            if (a == null) {
+                if (b == null) {
+                    return 0;
+                } else {
+                    return -1;
+                }
+            } else if (b == null) {
+                return 1;
+            } else if (a == b || a.equals(b)) {
+                return 0;
+            } else {
+                if (a.isAssignableFrom(b)) {
+                    return 1;
+                } else if (b.isAssignableFrom(a)) {
+                    return -1;
+                } else {
+                    return 0;
+                }
+            }
+        }
+    }
+
+    private static class InternalRegistry implements Map<Class, List<Subscriber>> {
+
+        private final Lock readLock;
+        private final Lock writeLock;
+        private final Map<Class,List<Subscriber>> map;
+
+        private InternalRegistry() {
+            this.map = new TreeMap<Class, List<Subscriber>>(new SubscribedClassComparator());
+            ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
+            readLock = rwl.readLock();
+            writeLock = rwl.writeLock();
+        }
+
+        public int size() {
+            readLock.lock();
+            try {
+                return map.size();
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        public boolean isEmpty() {
+            readLock.lock();
+            try {
+                return map.isEmpty();
+
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        public boolean containsKey(Object o) {
+            readLock.lock();
+            try {
+                return map.containsKey(o);
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        public boolean containsValue(Object o) {
+            readLock.lock();
+            try {
+                return map.containsValue(o);
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        public List<Subscriber> get(Object o) {
+            readLock.lock();
+            try {
+                return map.get(o);
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        public List<Subscriber> put(Class c, List<Subscriber> subscribers) {
+            writeLock.lock();
+            try {
+                return this.map.put(c, subscribers);
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        public List<Subscriber> remove(Object o) {
+            writeLock.lock();
+            try {
+                return this.map.remove(o);
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        public void putAll(Map<? extends Class, ? extends List<Subscriber>> map) {
+            writeLock.lock();
+            try {
+                this.map.putAll(map);
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        public void subscribe(Class c, Subscriber s) {
+            writeLock.lock();
+            try {
+                List<Subscriber> subscribers = this.map.get(c);
+                if (subscribers == null) {
+                    subscribers = new ArrayList<Subscriber>();
+                    this.map.put(c, subscribers);
+                }
+                if (!subscribers.contains(s)) {
+                    subscribers.add(s);
+                }
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        public void unsubscribe(Class c, Subscriber s) {
+            writeLock.lock();
+            try {
+                List<Subscriber> subscribers = this.map.get(c);
+                if (subscribers != null) {
+                    subscribers.remove(s);
+                }
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        public void unsubscribe(Subscriber s) {
+            writeLock.lock();
+            try {
+                for(Map.Entry<Class,List<Subscriber>> entry : this.map.entrySet()) {
+                    List<Subscriber> subscribers = entry.getValue();
+                    subscribers.remove(s);
+                }
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        public void clear() {
+            writeLock.lock();
+            try {
+                map.clear();
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        public Set<Class> keySet() {
+            readLock.lock();
+            try {
+                return Collections.unmodifiableSet(map.keySet());
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        public Collection<List<Subscriber>> values() {
+            readLock.lock();
+            try {
+                return Collections.unmodifiableCollection(map.values());
+            } finally {
+                readLock.unlock();
+            }
+        }
+
+        public Set<Entry<Class, List<Subscriber>>> entrySet() {
+            readLock.lock();
+            try {
+                return Collections.unmodifiableSet(map.entrySet());
+            } finally {
+                readLock.unlock();
+            }
+        }
+    }
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/InvalidSessionEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/InvalidSessionEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/InvalidSessionEvent.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/InvalidSessionEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,33 @@
+package org.apache.shiro.session.event;
+
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.SessionKey;
+
+/**
+ * @since 1.3
+ */
+public class InvalidSessionEvent extends SessionEvent {
+
+    private final SessionKey sessionKey;
+    private final InvalidSessionException exception;
+
+    public InvalidSessionEvent(Session session, SessionKey sessionKey, InvalidSessionException exception) {
+        super(session);
+        this.sessionKey = sessionKey;
+        this.exception = exception;
+    }
+
+    public SessionKey getSessionKey() {
+        return sessionKey;
+    }
+
+    public InvalidSessionException getException() {
+        return exception;
+    }
+
+    public boolean isSessionExpired() {
+        return exception instanceof ExpiredSessionException;
+    }
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/SessionEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/SessionEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/SessionEvent.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/SessionEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,21 @@
+package org.apache.shiro.session.event;
+
+import org.apache.shiro.event.ShiroEvent;
+import org.apache.shiro.session.Session;
+
+/**
+ * @since 1.3
+ */
+public abstract class SessionEvent extends ShiroEvent {
+
+    private final Session session;
+
+    public SessionEvent(Session session) {
+        super(session);
+        this.session = session;
+    }
+
+    public Session getSession() {
+        return session;
+    }
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StartedSessionEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StartedSessionEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StartedSessionEvent.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StartedSessionEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,21 @@
+package org.apache.shiro.session.event;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.SessionContext;
+
+/**
+ * @since 1.3
+ */
+public class StartedSessionEvent extends SessionEvent {
+
+    private final SessionContext sessionContext;
+
+    public StartedSessionEvent(Session session, SessionContext sessionContext) {
+        super(session);
+        this.sessionContext = sessionContext;
+    }
+
+    public SessionContext getSessionContext() {
+        return sessionContext;
+    }
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StoppedSessionEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StoppedSessionEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StoppedSessionEvent.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/event/StoppedSessionEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,21 @@
+package org.apache.shiro.session.event;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.SessionKey;
+
+/**
+ * @since 1.3
+ */
+public class StoppedSessionEvent extends SessionEvent {
+
+    private final SessionKey sessionKey;
+
+    public StoppedSessionEvent(Session session, SessionKey sessionKey) {
+        super(session);
+        this.sessionKey = sessionKey;
+    }
+
+    public SessionKey getSessionKey() {
+        return sessionKey;
+    }
+}

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/StandardSessionManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/StandardSessionManager.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/StandardSessionManager.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/StandardSessionManager.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,456 @@
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.event.Publisher;
+import org.apache.shiro.session.*;
+import org.apache.shiro.session.event.InvalidSessionEvent;
+import org.apache.shiro.session.event.SessionEvent;
+import org.apache.shiro.session.event.StartedSessionEvent;
+import org.apache.shiro.session.event.StoppedSessionEvent;
+import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
+import org.apache.shiro.util.Assert;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Destroyable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+
+/**
+ * @since 1.3
+ */
+public class StandardSessionManager implements NativeSessionManager, ValidatingSessionManager, CacheManagerAware, Destroyable {
+
+    private static final Logger log = LoggerFactory.getLogger(StandardSessionManager.class);
+
+    protected static final long MILLIS_PER_SECOND = 1000;
+    protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
+    public static final long DEFAULT_SESSION_TIMEOUT = 30 * MILLIS_PER_MINUTE; //30 minutes
+
+    private long defaultSessionTimeout = DEFAULT_SESSION_TIMEOUT;
+    private boolean deleteInvalidSessions;
+    private SessionValidationScheduler sessionValidationScheduler;
+    protected SessionFactory sessionFactory;
+    protected SessionDAO sessionDAO;
+    protected CacheManager cacheManager;
+    protected Publisher publisher;
+
+    public StandardSessionManager() {
+        this.sessionValidationScheduler = new ExecutorServiceSessionValidationScheduler(this);
+        this.deleteInvalidSessions = true;
+        this.sessionFactory = new SimpleSessionFactory();
+        this.sessionDAO = new MemorySessionDAO();
+    }
+
+    public long getDefaultSessionTimeout() {
+        return this.defaultSessionTimeout;
+    }
+
+    public void setDefaultSessionTimeout(long defaultSessionTimeout) {
+        this.defaultSessionTimeout = defaultSessionTimeout;
+    }
+
+    /**
+     * 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.
+     */
+    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.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public void setDeleteInvalidSessions(boolean deleteInvalidSessions) {
+        this.deleteInvalidSessions = deleteInvalidSessions;
+    }
+
+    public SessionValidationScheduler getSessionValidationScheduler() {
+        return sessionValidationScheduler;
+    }
+
+    public void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) {
+        this.sessionValidationScheduler = sessionValidationScheduler;
+    }
+
+    public SessionDAO getSessionDAO() {
+        return this.sessionDAO;
+    }
+
+    public void setSessionDAO(SessionDAO sessionDAO) {
+        this.sessionDAO = sessionDAO;
+        applyCacheManagerToSessionDAO();
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public CacheManager getCacheManager() {
+        return this.cacheManager;
+    }
+
+    public void setCacheManager(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+        applyCacheManagerToSessionDAO();
+    }
+
+    /**
+     * Sets the internal {@code CacheManager} on the {@code SessionDAO} if it implements the
+     * {@link org.apache.shiro.cache.CacheManagerAware CacheManagerAware} interface.
+     * <p/>
+     * This method is called after setting a cacheManager via the
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) setCacheManager} method <em>em</em> when
+     * setting a {@code SessionDAO} via the {@link #setSessionDAO} method to allow it to be propagated
+     * in either case.
+     */
+    private void applyCacheManagerToSessionDAO() {
+        if (this.cacheManager != null && this.sessionDAO != null && this.sessionDAO instanceof CacheManagerAware) {
+            ((CacheManagerAware) this.sessionDAO).setCacheManager(this.cacheManager);
+        }
+    }
+
+    /**
+     * Returns the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
+     * is a {@link SimpleSessionFactory}.
+     *
+     * @return the {@code SessionFactory} used to generate new {@link Session} instances.
+     */
+    public SessionFactory getSessionFactory() {
+        return sessionFactory;
+    }
+
+    /**
+     * Sets the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
+     * is a {@link SimpleSessionFactory}.
+     *
+     * @param sessionFactory the {@code SessionFactory} used to generate new {@link Session} instances.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public Publisher getPublisher() {
+        return publisher;
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setPublisher(Publisher publisher) {
+        this.publisher = publisher;
+    }
+
+    /* =====================================================================
+       Destroyable implementation
+       ===================================================================== */
+
+    public void destroy() throws Exception {
+        if (this.sessionValidationScheduler != null) {
+            this.sessionValidationScheduler.disableSessionValidation();
+        }
+    }
+
+    /* =====================================================================
+       SessionManager implementation
+       ===================================================================== */
+
+    public Session start(SessionContext context) {
+        enableSessionValidationIfNecessary();
+        Session internal = createInternalSession(context);
+        //Don't expose the EIS-tier Session object to the client-tier:
+        return createExposedSession(internal, context);
+    }
+
+    protected Session createInternalSession(SessionContext context) {
+
+        Session session = getSessionFactory().createSession(context);
+        if (log.isTraceEnabled()) {
+            log.trace("Creating session for host {}", session.getHost());
+        }
+
+        session.setTimeout(getDefaultSessionTimeout());
+
+        if (log.isDebugEnabled()) {
+            log.debug("Creating new EIS record for new session instance [" + session + "]");
+        }
+
+        createInternalSession(session, context);
+
+        StartedSessionEvent event = new StartedSessionEvent(session, context);
+        notify(event);
+
+        return session;
+    }
+
+    protected void createInternalSession(Session session, SessionContext context) {
+        getSessionDAO().create(session);
+    }
+
+    public Session getSession(SessionKey key) throws SessionException {
+        enableSessionValidationIfNecessary();
+        Session session = getInternalSession(key);
+        return session != null ? createExposedSession(session, key) : null;
+    }
+
+    protected Session getInternalSession(SessionKey key) {
+
+        log.trace("Attempting to retrieve session with key {}", key);
+
+        Serializable sessionId = getSessionId(key);
+        if (sessionId == null) {
+            log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a " +
+                    "session could not be found.", key);
+            return null;
+        }
+        Session session = getInternalSession(key, sessionId);
+        if (session == null) {
+            //session ID was provided, meaning one is expected to be found, but we couldn't find one:
+            String msg = "Could not find session with ID [" + sessionId + "]";
+            throw new UnknownSessionException(msg);
+        }
+
+        validate(session, key);
+
+        return session;
+    }
+
+    protected Session getInternalSession(SessionKey sessionKey, Serializable resolvedSessionId) {
+        return getSessionDAO().readSession(resolvedSessionId);
+    }
+
+    protected final void enableSessionValidationIfNecessary() {
+        if (this.sessionValidationScheduler != null && !this.sessionValidationScheduler.isEnabled()) {
+            this.sessionValidationScheduler.enableSessionValidation();
+        }
+    }
+
+    protected Serializable getSessionId(SessionKey sessionKey) {
+        return sessionKey.getSessionId();
+    }
+
+    /* =====================================================================
+       ValidatingSessionManager methods
+       ===================================================================== */
+
+    public void validateSessions() {
+        log.debug("Validating active sessions...");
+
+        int invalidCount = 0;
+
+        Collection<Session> activeSessions = getSessionDAO().getActiveSessions();
+
+        if (activeSessions != null) {
+            for (Session s : activeSessions) {
+                try {
+                    //simulate a lookup key to satisfy the method signature.
+                    //this could probably stand to be cleaned up in future versions:
+                    SessionKey key = new DefaultSessionKey(s.getId());
+                    validate(s, key);
+                } catch (InvalidSessionException e) {
+                    if (log.isTraceEnabled()) {
+                        boolean expired = (e instanceof ExpiredSessionException);
+                        String msg = "Invalidated session with id [" + s.getId() + "]" +
+                                (expired ? " (expired)" : " (stopped)");
+                        log.trace(msg);
+                    }
+                    invalidCount++;
+                }
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            String msg = "Finished session validation.";
+            if (invalidCount > 0) {
+                msg += "  [" + invalidCount + "] sessions were stopped.";
+            } else {
+                msg += "  No sessions were stopped.";
+            }
+            log.debug(msg);
+        }
+    }
+
+    protected void validate(Session session, SessionKey key) throws InvalidSessionException {
+        Assert.isInstanceOf(ValidatingSession.class, session, StandardSessionManager.class.getName() +
+                " implementations require native sessions to implement " + ValidatingSession.class.getName());
+
+        try {
+            ((ValidatingSession) session).validate();
+        } catch (InvalidSessionException ise) {
+            onStop(session, key, ise);
+            throw ise;
+        }
+    }
+
+    protected void onStop(Session session, SessionKey key, InvalidSessionException ise) {
+
+        boolean expired = ise instanceof ExpiredSessionException;
+
+        if (session instanceof SimpleSession) {
+            SimpleSession ss = (SimpleSession) session;
+            if (expired) {
+                ss.setExpired(expired);
+            } else {
+                Date stopTs = ss.getStopTimestamp();
+                ss.setLastAccessTime(stopTs);
+            }
+        }
+
+        Session immutable = beforeStopNotification(session);
+        SessionEvent event = (ise != null) ?
+                new InvalidSessionEvent(immutable, key, ise) :
+                new StoppedSessionEvent(immutable, key);
+        notify(event);
+
+        if (isDeleteInvalidSessions()) {
+            delete(session, key);
+        } else {
+            update(session, key);
+        }
+    }
+
+    protected void delete(Session session, SessionKey key) {
+        log.debug("Deleting DAO session {}", session.getId());
+        getSessionDAO().delete(session);
+    }
+
+    protected void update(Session session, SessionKey key) {
+        log.debug("Updating DAO session {}", session.getId());
+        getSessionDAO().update(session);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void notify(Object event) {
+        if (event != null && this.publisher != null) {
+            this.publisher.publish(event);
+        }
+    }
+
+    protected Session createExposedSession(Session session, Object context) {
+        return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
+    }
+
+    /**
+     * Returns the session instance to use to pass to registered {@code SessionListener}s for notification
+     * that the session has been stopped (stopped or expired).
+     * <p/>
+     * The default implementation returns an {@link ImmutableProxiedSession ImmutableProxiedSession} instance to ensure
+     * that the specified {@code session} argument is not modified by any listeners.
+     *
+     * @param session the stopped {@code Session}.
+     * @return the {@code Session} instance to use for {@link #notify(Object) notification}.
+     */
+    protected Session beforeStopNotification(Session session) {
+        return new ImmutableProxiedSession(session);
+    }
+
+    /* =====================================================================
+       NativeSessionManager implementation
+       ===================================================================== */
+
+    public Date getStartTimestamp(SessionKey key) {
+        return getInternalSession(key).getStartTimestamp();
+    }
+
+    public Date getLastAccessTime(SessionKey key) {
+        return getInternalSession(key).getLastAccessTime();
+    }
+
+    public long getTimeout(SessionKey key) throws InvalidSessionException {
+        return getInternalSession(key).getTimeout();
+    }
+
+    public void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException {
+        Session session = getInternalSession(key);
+        session.setTimeout(maxIdleTimeInMillis);
+        update(session, key);
+    }
+
+    public void touch(SessionKey key) throws InvalidSessionException {
+        Session session = getInternalSession(key);
+        session.touch();
+        update(session, key);
+    }
+
+    public String getHost(SessionKey key) {
+        return getInternalSession(key).getHost();
+    }
+
+    public Collection<Object> getAttributeKeys(SessionKey key) {
+        Collection<Object> c = getInternalSession(key).getAttributeKeys();
+        if (!CollectionUtils.isEmpty(c)) {
+            return Collections.unmodifiableCollection(c);
+        }
+        return Collections.emptySet();
+    }
+
+    public Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
+        return getInternalSession(sessionKey).getAttribute(attributeKey);
+    }
+
+    public void setAttribute(SessionKey key, Object attributeKey, Object value) throws InvalidSessionException {
+        if (value == null) {
+            removeAttribute(key, attributeKey);
+        } else {
+            Session s = getInternalSession(key);
+            s.setAttribute(attributeKey, value);
+            update(s, key);
+        }
+    }
+
+    public Object removeAttribute(SessionKey key, Object attributeKey) throws InvalidSessionException {
+        Session session = getInternalSession(key);
+        Object removed = session.removeAttribute(attributeKey);
+        if (removed != null) {
+            update(session, key);
+        }
+        return removed;
+    }
+
+    public boolean isValid(SessionKey key) {
+        try {
+            checkValid(key);
+            return true;
+        } catch (InvalidSessionException e) {
+            return false;
+        }
+    }
+
+    public void stop(SessionKey key) throws InvalidSessionException {
+        Session session = getInternalSession(key);
+        if (log.isDebugEnabled()) {
+            log.debug("Stopping session with id [" + session.getId() + "]");
+        }
+        session.stop();
+        onStop(session, key, null);
+    }
+
+    public void checkValid(SessionKey key) throws InvalidSessionException {
+        //just try to acquire it.  If there is a problem, an exception will be thrown:
+        getInternalSession(key);
+    }
+}

Modified: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java?rev=1351193&r1=1351192&r2=1351193&view=diff
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java (original)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java Mon Jun 18 02:01:11 2012
@@ -22,6 +22,7 @@ import org.apache.shiro.session.Session;
 
 import java.io.Serializable;
 import java.util.UUID;
+import java.util.regex.Pattern;
 
 /**
  * {@link SessionIdGenerator} that generates String values of JDK {@link java.util.UUID}'s as the session IDs.
@@ -30,6 +31,8 @@ import java.util.UUID;
  */
 public class JavaUuidSessionIdGenerator implements SessionIdGenerator {
 
+    private final Pattern PATTERN = Pattern.compile("-");
+
     /**
      * Ignores the method argument and simply returns
      * {@code UUID}.{@link java.util.UUID#randomUUID() randomUUID()}.{@code toString()}.
@@ -38,6 +41,7 @@ public class JavaUuidSessionIdGenerator 
      * @return the String value of the JDK's next {@link UUID#randomUUID() randomUUID()}.
      */
     public Serializable generateId(Session session) {
-        return UUID.randomUUID().toString();
+        String id = UUID.randomUUID().toString();
+        return PATTERN.matcher(id).replaceAll("").toUpperCase();
     }
 }

Modified: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java?rev=1351193&r1=1351192&r2=1351193&view=diff
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java (original)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java Mon Jun 18 02:01:11 2012
@@ -74,10 +74,12 @@ public class MemorySessionDAO extends Ab
         if (id == null) {
             throw new NullPointerException("id argument cannot be null.");
         }
+        log.trace("Storing session {}.", id);
         return sessions.putIfAbsent(id, session);
     }
 
     protected Session doReadSession(Serializable sessionId) {
+        log.trace("Retrieving session {}", sessionId);
         return sessions.get(sessionId);
     }
 
@@ -91,6 +93,7 @@ public class MemorySessionDAO extends Ab
         }
         Serializable id = session.getId();
         if (id != null) {
+            log.trace("Removing session {}", id);
             sessions.remove(id);
         }
     }

Added: shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/util/Assert.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/util/Assert.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/util/Assert.java (added)
+++ shiro/branches/SHIRO-317b/core/src/main/java/org/apache/shiro/util/Assert.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,387 @@
+package org.apache.shiro.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Assertion utility class that assists in validating arguments.
+ * Useful for identifying programmer errors early and clearly at runtime.
+ *
+ * <p>For example, if the contract of a public method states it does not
+ * allow <code>null</code> arguments, Assert can be used to validate that
+ * contract. Doing this clearly indicates a contract violation when it
+ * occurs and protects the class's invariants.
+ *
+ * <p>Typically used to validate method arguments rather than configuration
+ * properties, to check for cases that are usually programmer errors rather than
+ * configuration errors. In contrast to config initialization code, there is
+ * usally no point in falling back to defaults in such methods.
+ *
+ * <p>This class is similar to JUnit's assertion library. If an argument value is
+ * deemed invalid, an {@link IllegalArgumentException} is thrown (typically).
+ * For example:
+ *
+ * <pre class="code">
+ * Assert.notNull(clazz, "The class must not be null");
+ * Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
+ *
+ * Mainly for internal use within the framework; consider Jakarta's Commons Lang
+ * >= 2.0 for a more comprehensive suite of assertion utilities.
+ * <p/>
+ * <em>Gratefully borrowed from the Spring Framework, also Apache 2.0 licensed</em>
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Colin Sampaleanu
+ * @author Rob Harrop
+ * @since 1.3
+ */
+public abstract class Assert {
+
+    /**
+     * Assert a boolean expression, throwing <code>IllegalArgumentException</code>
+     * if the test result is <code>false</code>.
+     * <pre class="code">Assert.isTrue(i &gt; 0, "The value must be greater than zero");</pre>
+     * @param expression a boolean expression
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalArgumentException if expression is <code>false</code>
+     */
+    public static void isTrue(boolean expression, String message) {
+        if (!expression) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert a boolean expression, throwing <code>IllegalArgumentException</code>
+     * if the test result is <code>false</code>.
+     * <pre class="code">Assert.isTrue(i &gt; 0);</pre>
+     * @param expression a boolean expression
+     * @throws IllegalArgumentException if expression is <code>false</code>
+     */
+    public static void isTrue(boolean expression) {
+        isTrue(expression, "[Assertion failed] - this expression must be true");
+    }
+
+    /**
+     * Assert that an object is <code>null</code> .
+     * <pre class="code">Assert.isNull(value, "The value must be null");</pre>
+     * @param object the object to check
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalArgumentException if the object is not <code>null</code>
+     */
+    public static void isNull(Object object, String message) {
+        if (object != null) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that an object is <code>null</code> .
+     * <pre class="code">Assert.isNull(value);</pre>
+     * @param object the object to check
+     * @throws IllegalArgumentException if the object is not <code>null</code>
+     */
+    public static void isNull(Object object) {
+        isNull(object, "[Assertion failed] - the object argument must be null");
+    }
+
+    /**
+     * Assert that an object is not <code>null</code> .
+     * <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
+     * @param object the object to check
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalArgumentException if the object is <code>null</code>
+     */
+    public static void notNull(Object object, String message) {
+        if (object == null) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that an object is not <code>null</code> .
+     * <pre class="code">Assert.notNull(clazz);</pre>
+     * @param object the object to check
+     * @throws IllegalArgumentException if the object is <code>null</code>
+     */
+    public static void notNull(Object object) {
+        notNull(object, "[Assertion failed] - this argument is required; it must not be null");
+    }
+
+    /**
+     * Assert that the given String is not empty; that is,
+     * it must not be <code>null</code> and not the empty String.
+     * <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
+     * @param text the String to check
+     * @param message the exception message to use if the assertion fails
+     * @see StringUtils#hasLength
+     */
+    public static void hasLength(String text, String message) {
+        if (!StringUtils.hasLength(text)) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that the given String is not empty; that is,
+     * it must not be <code>null</code> and not the empty String.
+     * <pre class="code">Assert.hasLength(name);</pre>
+     * @param text the String to check
+     * @see StringUtils#hasLength
+     */
+    public static void hasLength(String text) {
+        hasLength(text,
+                "[Assertion failed] - this String argument must have length; it must not be null or empty");
+    }
+
+    /**
+     * Assert that the given String has valid text content; that is, it must not
+     * be <code>null</code> and must contain at least one non-whitespace character.
+     * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+     * @param text the String to check
+     * @param message the exception message to use if the assertion fails
+     * @see StringUtils#hasText
+     */
+    public static void hasText(String text, String message) {
+        if (!StringUtils.hasText(text)) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that the given String has valid text content; that is, it must not
+     * be <code>null</code> and must contain at least one non-whitespace character.
+     * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+     * @param text the String to check
+     * @see StringUtils#hasText
+     */
+    public static void hasText(String text) {
+        hasText(text,
+                "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
+    }
+
+    /**
+     * Assert that the given text does not contain the given substring.
+     * <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
+     * @param textToSearch the text to search
+     * @param substring the substring to find within the text
+     * @param message the exception message to use if the assertion fails
+     */
+    public static void doesNotContain(String textToSearch, String substring, String message) {
+        if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) &&
+                textToSearch.indexOf(substring) != -1) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that the given text does not contain the given substring.
+     * <pre class="code">Assert.doesNotContain(name, "rod");</pre>
+     * @param textToSearch the text to search
+     * @param substring the substring to find within the text
+     */
+    public static void doesNotContain(String textToSearch, String substring) {
+        doesNotContain(textToSearch, substring,
+                "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
+    }
+
+
+    /**
+     * Assert that an array has elements; that is, it must not be
+     * <code>null</code> and must have at least one element.
+     * <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre>
+     * @param array the array to check
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
+     */
+    public static void notEmpty(Object[] array, String message) {
+        if (array == null || array.length == 0) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that an array has elements; that is, it must not be
+     * <code>null</code> and must have at least one element.
+     * <pre class="code">Assert.notEmpty(array);</pre>
+     * @param array the array to check
+     * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
+     */
+    public static void notEmpty(Object[] array) {
+        notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
+    }
+
+    /**
+     * Assert that an array has no null elements.
+     * Note: Does not complain if the array is empty!
+     * <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
+     * @param array the array to check
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalArgumentException if the object array contains a <code>null</code> element
+     */
+    public static void noNullElements(Object[] array, String message) {
+        if (array != null) {
+            for (int i = 0; i < array.length; i++) {
+                if (array[i] == null) {
+                    throw new IllegalArgumentException(message);
+                }
+            }
+        }
+    }
+
+    /**
+     * Assert that an array has no null elements.
+     * Note: Does not complain if the array is empty!
+     * <pre class="code">Assert.noNullElements(array);</pre>
+     * @param array the array to check
+     * @throws IllegalArgumentException if the object array contains a <code>null</code> element
+     */
+    public static void noNullElements(Object[] array) {
+        noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
+    }
+
+    /**
+     * Assert that a collection has elements; that is, it must not be
+     * <code>null</code> and must have at least one element.
+     * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+     * @param collection the collection to check
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
+     */
+    public static void notEmpty(Collection collection, String message) {
+        if (CollectionUtils.isEmpty(collection)) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that a collection has elements; that is, it must not be
+     * <code>null</code> and must have at least one element.
+     * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+     * @param collection the collection to check
+     * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
+     */
+    public static void notEmpty(Collection collection) {
+        notEmpty(collection,
+                "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
+    }
+
+    /**
+     * Assert that a Map has entries; that is, it must not be <code>null</code>
+     * and must have at least one entry.
+     * <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre>
+     * @param map the map to check
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
+     */
+    public static void notEmpty(Map map, String message) {
+        if (CollectionUtils.isEmpty(map)) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    /**
+     * Assert that a Map has entries; that is, it must not be <code>null</code>
+     * and must have at least one entry.
+     * <pre class="code">Assert.notEmpty(map);</pre>
+     * @param map the map to check
+     * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
+     */
+    public static void notEmpty(Map map) {
+        notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
+    }
+
+
+    /**
+     * Assert that the provided object is an instance of the provided class.
+     * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
+     * @param clazz the required class
+     * @param obj the object to check
+     * @throws IllegalArgumentException if the object is not an instance of clazz
+     * @see Class#isInstance
+     */
+    public static void isInstanceOf(Class clazz, Object obj) {
+        isInstanceOf(clazz, obj, "");
+    }
+
+    /**
+     * Assert that the provided object is an instance of the provided class.
+     * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
+     * @param type the type to check against
+     * @param obj the object to check
+     * @param message a message which will be prepended to the message produced by
+     * the function itself, and which may be used to provide context. It should
+     * normally end in a ": " or ". " so that the function generate message looks
+     * ok when prepended to it.
+     * @throws IllegalArgumentException if the object is not an instance of clazz
+     * @see Class#isInstance
+     */
+    public static void isInstanceOf(Class type, Object obj, String message) {
+        notNull(type, "Type to check against must not be null");
+        if (!type.isInstance(obj)) {
+            throw new IllegalArgumentException(message +
+                    "Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
+                    "] must be an instance of " + type);
+        }
+    }
+
+    /**
+     * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
+     * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
+     * @param superType the super type to check
+     * @param subType the sub type to check
+     * @throws IllegalArgumentException if the classes are not assignable
+     */
+    public static void isAssignable(Class superType, Class subType) {
+        isAssignable(superType, subType, "");
+    }
+
+    /**
+     * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
+     * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
+     * @param superType the super type to check against
+     * @param subType the sub type to check
+     * @param message a message which will be prepended to the message produced by
+     * the function itself, and which may be used to provide context. It should
+     * normally end in a ": " or ". " so that the function generate message looks
+     * ok when prepended to it.
+     * @throws IllegalArgumentException if the classes are not assignable
+     */
+    public static void isAssignable(Class superType, Class subType, String message) {
+        notNull(superType, "Type to check against must not be null");
+        if (subType == null || !superType.isAssignableFrom(subType)) {
+            throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
+        }
+    }
+
+
+    /**
+     * Assert a boolean expression, throwing <code>IllegalStateException</code>
+     * if the test result is <code>false</code>. Call isTrue if you wish to
+     * throw IllegalArgumentException on an assertion failure.
+     * <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
+     * @param expression a boolean expression
+     * @param message the exception message to use if the assertion fails
+     * @throws IllegalStateException if expression is <code>false</code>
+     */
+    public static void state(boolean expression, String message) {
+        if (!expression) {
+            throw new IllegalStateException(message);
+        }
+    }
+
+    /**
+     * Assert a boolean expression, throwing {@link IllegalStateException}
+     * if the test result is <code>false</code>.
+     * <p>Call {@link #isTrue(boolean)} if you wish to
+     * throw {@link IllegalArgumentException} on an assertion failure.
+     * <pre class="code">Assert.state(id == null);</pre>
+     * @param expression a boolean expression
+     * @throws IllegalStateException if the supplied expression is <code>false</code>
+     */
+    public static void state(boolean expression) {
+        state(expression, "[Assertion failed] - this state invariant must be true");
+    }
+
+}

Propchange: shiro/branches/SHIRO-317b/samples/guice/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Jun 18 02:01:11 2012
@@ -0,0 +1 @@
+*.iml

Propchange: shiro/branches/SHIRO-317b/samples/quickstart-guice/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Jun 18 02:01:11 2012
@@ -0,0 +1 @@
+*.iml

Modified: shiro/branches/SHIRO-317b/samples/web/pom.xml
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/samples/web/pom.xml?rev=1351193&r1=1351192&r2=1351193&view=diff
==============================================================================
--- shiro/branches/SHIRO-317b/samples/web/pom.xml (original)
+++ shiro/branches/SHIRO-317b/samples/web/pom.xml Mon Jun 18 02:01:11 2012
@@ -113,6 +113,7 @@
         <dependency>
             <groupId>taglibs</groupId>
             <artifactId>standard</artifactId>
+            <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>

Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/BeginServletRequestEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/BeginServletRequestEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/BeginServletRequestEvent.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/BeginServletRequestEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,18 @@
+package org.apache.shiro.web.event;
+
+import org.apache.shiro.subject.Subject;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Event triggered at the beginning of a Shiro-filtered servlet request, before the filter chain has been invoked.
+ *
+ * @since 1.3
+ */
+public class BeginServletRequestEvent extends ServletRequestEvent {
+
+    public BeginServletRequestEvent(Subject subject, ServletRequest servletRequest, ServletResponse servletResponse) {
+        super(subject, servletRequest, servletResponse);
+    }
+}

Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/EndServletRequestEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/EndServletRequestEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/EndServletRequestEvent.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/EndServletRequestEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,32 @@
+package org.apache.shiro.web.event;
+
+import org.apache.shiro.subject.Subject;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Event triggered at the end of a Shiro-filtered servlet request.
+ *
+ * @since 1.3
+ */
+public class EndServletRequestEvent extends ServletRequestEvent {
+
+    private final Throwable throwable;
+
+    public EndServletRequestEvent(Subject subject, ServletRequest servletRequest, ServletResponse servletResponse, Throwable t) {
+        super(subject, servletRequest, servletResponse);
+        this.throwable = t;
+    }
+
+    /**
+     * Returns any Throwable that might have resulted during request execution or {@code null} if no Throwable was
+     * triggered.
+     *
+     * @return any Throwable that might have resulted during request execution or {@code null} if no Throwable was
+     *         triggered.
+     */
+    public Throwable getThrowable() {
+        return throwable;
+    }
+}

Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/ServletRequestEvent.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/ServletRequestEvent.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/ServletRequestEvent.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/event/ServletRequestEvent.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,32 @@
+package org.apache.shiro.web.event;
+
+import org.apache.shiro.event.SubjectEvent;
+import org.apache.shiro.subject.Subject;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * An event triggered during the lifecycle of a Shiro-filtered ServletRequest.
+ *
+ * @since 1.3
+ */
+public class ServletRequestEvent extends SubjectEvent {
+
+    private final ServletRequest servletRequest;
+    private final ServletResponse servletResponse;
+
+    public ServletRequestEvent(Subject subject, ServletRequest servletRequest, ServletResponse servletResponse) {
+        super(subject);
+        this.servletRequest = servletRequest;
+        this.servletResponse = servletResponse;
+    }
+
+    public ServletRequest getServletRequest() {
+        return servletRequest;
+    }
+
+    public ServletResponse getServletResponse() {
+        return servletResponse;
+    }
+}

Modified: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java?rev=1351193&r1=1351192&r2=1351193&view=diff
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java (original)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java Mon Jun 18 02:01:11 2012
@@ -19,9 +19,18 @@
 package org.apache.shiro.web.servlet;
 
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.event.Publisher;
+import org.apache.shiro.event.Subscriber;
+import org.apache.shiro.event.SynchronousEventBus;
+import org.apache.shiro.mgt.SessionsSecurityManager;
 import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.subject.ExecutionException;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+import org.apache.shiro.web.event.BeginServletRequestEvent;
+import org.apache.shiro.web.event.EndServletRequestEvent;
+import org.apache.shiro.web.event.ServletRequestEvent;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 import org.apache.shiro.web.mgt.WebSecurityManager;
@@ -36,6 +45,7 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.UUID;
 import java.util.concurrent.Callable;
 
 /**
@@ -68,13 +78,15 @@ import java.util.concurrent.Callable;
  * See the Shiro <a href="http://shiro.apache.org/subject.html">Subject documentation</a> for more information as to
  * if you would do this, particularly the sections on the {@code Subject.Builder} and Thread Association.
  *
- * @since 1.0
  * @see <a href="http://shiro.apache.org/subject.html">Subject documentation</a>
+ * @since 1.0
  */
 public abstract class AbstractShiroFilter extends OncePerRequestFilter {
 
     private static final Logger log = LoggerFactory.getLogger(AbstractShiroFilter.class);
 
+    public static final String REQUEST_ID_ATTR_NAME = "ApacheShiroRequestId";
+
     private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";
 
     // Reference to the security manager used by this filter
@@ -83,9 +95,14 @@ public abstract class AbstractShiroFilte
     // Used to determine which chain should handle an incoming request/response
     private FilterChainResolver filterChainResolver;
 
+    // Used to publish various events of interest
+    // since 1.3
+    private Publisher publisher;
+
     /**
      * Whether or not to bind the constructed SecurityManager instance to static memory (via
      * SecurityUtils.setSecurityManager).  This was added to support https://issues.apache.org/jira/browse/SHIRO-287
+     *
      * @since 1.2
      */
     private boolean staticSecurityManagerEnabled;
@@ -110,6 +127,16 @@ public abstract class AbstractShiroFilte
         this.filterChainResolver = filterChainResolver;
     }
 
+    //since 1.3
+    public Publisher getPublisher() {
+        return publisher;
+    }
+
+    //since 1.3
+    public void setPublisher(Publisher publisher) {
+        this.publisher = publisher;
+    }
+
     /**
      * Returns {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
      * to static memory (via
@@ -119,12 +146,11 @@ public abstract class AbstractShiroFilte
      * The default value is {@code false}.
      * <p/>
      *
-     *
      * @return {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
      *         to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
      *         {@code false} otherwise.
-     * @since 1.2
      * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     * @since 1.2
      */
     public boolean isStaticSecurityManagerEnabled() {
         return staticSecurityManagerEnabled;
@@ -137,10 +163,10 @@ public abstract class AbstractShiroFilte
      * The default value is {@code false}.
      *
      * @param staticSecurityManagerEnabled if the constructed {@link #getSecurityManager() securityManager} reference
-     *                                       should be bound to static memory (via
-     *                                       {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
-     * @since 1.2
+     *                                     should be bound to static memory (via
+     *                                     {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
      * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     * @since 1.2
      */
     public void setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
         this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
@@ -155,14 +181,18 @@ public abstract class AbstractShiroFilte
         if (isStaticSecurityManagerEnabled()) {
             SecurityUtils.setSecurityManager(getSecurityManager());
         }
+        //since 1.3: TODO: this should be done in configuration, not here. WORK IN PROGRESS.
+        ensurePublisher();
+        //since 1.3 TODO: this should be done in configuration, not here. WORK IN PROGRESS.
+        subscribeSessionManager();
     }
 
     /**
      * Checks if the init-param that configures the filter to use static memory has been configured, and if so,
      * sets the {@link #setStaticSecurityManagerEnabled(boolean)} attribute with the configured value.
      *
-     * @since 1.2
      * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     * @since 1.2
      */
     private void applyStaticSecurityManagerEnabledConfig() {
         String value = getInitParam(STATIC_INIT_PARAM_NAME);
@@ -191,6 +221,25 @@ public abstract class AbstractShiroFilte
         }
     }
 
+    //since 1.3: TODO: this should be done in configuration, not here. WORK IN PROGRESS.
+    private void ensurePublisher() {
+        if (this.publisher == null) {
+            this.publisher = new SynchronousEventBus();
+        }
+    }
+
+    //since 1.3 TODO: this should be done in configuration, not here. WORK IN PROGRESS.
+    private void subscribeSessionManager() {
+        WebSecurityManager securityManager = getSecurityManager();
+        if (securityManager instanceof SessionsSecurityManager) {
+            SessionManager sessionManager = ((SessionsSecurityManager) securityManager).getSessionManager();
+            if (sessionManager instanceof Subscriber && this.publisher instanceof SynchronousEventBus) {
+                Subscriber subscriber = (Subscriber) sessionManager;
+                ((SynchronousEventBus) this.publisher).subscribe(subscriber, ServletRequestEvent.class);
+            }
+        }
+    }
+
     protected WebSecurityManager createDefaultSecurityManager() {
         return new DefaultWebSecurityManager();
     }
@@ -347,31 +396,50 @@ public abstract class AbstractShiroFilte
      * @throws IOException                    if an IO error occurs
      * @throws javax.servlet.ServletException if an Throwable other than an IOException
      */
+    @SuppressWarnings("unchecked")
     protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
             throws ServletException, IOException {
 
+        ServletRequest request = null;
+        ServletResponse response = null;
+        Subject subject = null;
         Throwable t = null;
 
         try {
-            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
-            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
-
-            final Subject subject = createSubject(request, response);
-
-            //noinspection unchecked
-            subject.execute(new Callable() {
-                public Object call() throws Exception {
-                    updateSessionLastAccessTime(request, response);
-                    executeChain(request, response, chain);
-                    return null;
-                }
-            });
+            final ServletRequest finalRequest = request = prepareServletRequest(servletRequest, servletResponse, chain);
+            final ServletResponse finalResponse = response = prepareServletResponse(request, servletResponse, chain);
+            subject = createSubject(request, response);
+
+            getPublisher().publish(new BeginServletRequestEvent(subject, request, response));
+
+            try {
+                subject.execute(new Callable() {
+                    public Object call() throws Exception {
+                        updateSessionLastAccessTimeIfNecessary(finalRequest, finalResponse);
+                        executeChain(finalRequest, finalResponse, chain);
+                        return null;
+                    }
+                });
+            } finally {
+                ThreadContext.remove(); //silence innocuous Tomcat ThreadLocal warnings
+            }
         } catch (ExecutionException ex) {
             t = ex.getCause();
         } catch (Throwable throwable) {
             t = throwable;
         }
 
+        try {
+            getPublisher().publish(new EndServletRequestEvent(subject, request, response, t));
+        } catch (Throwable t2) {
+            if (t == null) {
+                t = t2;
+            } else {
+                log.warn("afterCompletion resulted in an unexpected Throwable.  This will be ignored and logged " +
+                        "here the original request exception will be propagated instead.", t2);
+            }
+        }
+
         if (t != null) {
             if (t instanceof ServletException) {
                 throw (ServletException) t;
@@ -385,6 +453,26 @@ public abstract class AbstractShiroFilte
         }
     }
 
+    //since 1.3, added for backwards compatibility only!
+    protected final void updateSessionLastAccessTimeIfNecessary(ServletRequest request, ServletResponse response) {
+        WebSecurityManager securityManager = getSecurityManager();
+        if (securityManager.isHttpSessionMode()) {
+            return;
+        }
+
+        if (securityManager instanceof SessionsSecurityManager) {
+            SessionManager manager = ((SessionsSecurityManager) securityManager).getSessionManager();
+            if (manager instanceof Subscriber) {
+                //session accessTimestamp updates should be handled by BeginServletRequestEvents, not in the
+                //ShiroFilter:
+                return;
+            }
+        }
+
+        //new 1.3 components were not found, revert to < 1.3 behavior for backwards compatibility:
+        updateSessionLastAccessTime(request, response);
+    }
+
     /**
      * Returns the {@code FilterChain} to execute for the given request.
      * <p/>

Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/AccessTimestampEvaluator.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/AccessTimestampEvaluator.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/AccessTimestampEvaluator.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/AccessTimestampEvaluator.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,39 @@
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.web.event.BeginServletRequestEvent;
+
+/**
+ * Component that allows customization of when a valid session's lastAccessTimestamp will be updated.
+ * <p/>
+ * The Servlet Specification specifies that a session lastAccessTimestamp will be updated on each request that is
+ * associated with a valid session.  Default implementations of this interface enforce this (expected) behavior.
+ * <p/>
+ * <b>WARNING:</b> Be careful about changing the default behavior of these implementations or writing your own.
+ * If sessions' last access timestamps are not updated properly, they will not time out as expected, which could
+ * introduce security attack vectors.
+ * <p/>
+ * However, scrutinized implementations of this interface can be useful in certain scenarios, depending on business
+ * and/or security requirements.
+ *
+ * @see <a href="http://www.scribd.com/doc/23278127/58/SRV-7-6-Last-Accessed-Times">Servlet Specification, Section 7.6</a>
+ * @since 1.3
+ */
+public interface AccessTimestampEvaluator {
+
+    /**
+     * Returns {@code true} if a request with an associated valid session should result in updating the session's
+     * {@code lastAccessTimestamp} to the current time this method is invoked, {@code false} otherwise.
+     * <p/>
+     * <b>Servlet Specification-compliant implementations always return {@code true}</b> to guarantee default
+     * session behavior.  However, returning false may be useful in certain scenarios depending on business and/or
+     * security requirements.  If you implement this interface, return {@code false} judiciously.
+     * <p/>
+     * See the class-level JavaDoc for more.
+     *
+     * @param event the event that indicates a request is starting (but not yet propagated down the servlet filter
+     *              chain).
+     * @return {@code true} if a request with an associated valid session should result in updating the session's
+     *         {@code lastAccessTimestamp} to the current time this method is invoked, {@code false} otherwise.
+     */
+    boolean isUpdateAccessTimestamp(BeginServletRequestEvent event);
+}

Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/RequestIdGenerator.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/RequestIdGenerator.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/RequestIdGenerator.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/RequestIdGenerator.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,21 @@
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.web.event.BeginServletRequestEvent;
+
+/**
+ * Generates globally unique IDs to assign to ServletRequests (for example, as a request attribute).  This is
+ * useful for debugging in multi-threaded/multi-request environments.
+ *
+ * @since 1.3
+ */
+public interface RequestIdGenerator {
+
+    /**
+     * Returns a globally unique request ID to be associated with an incoming ServletRequest, or {@code null} if no
+     * ID should be associated.  Useful for debugging in multi-threaded/multi-request environments.
+     *
+     * @return a globally unique request ID to be associated with an incoming ServletRequest, or {@code null} if no
+     *         ID should be associated.
+     */
+    String generateId(BeginServletRequestEvent event);
+}

Added: shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/SpecCompliantAccessTimestampEvaluator.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/SpecCompliantAccessTimestampEvaluator.java?rev=1351193&view=auto
==============================================================================
--- shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/SpecCompliantAccessTimestampEvaluator.java (added)
+++ shiro/branches/SHIRO-317b/web/src/main/java/org/apache/shiro/web/session/mgt/SpecCompliantAccessTimestampEvaluator.java Mon Jun 18 02:01:11 2012
@@ -0,0 +1,18 @@
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.web.event.BeginServletRequestEvent;
+
+/**
+ * Servlet Specification-compliant implementation that always returns {@code true}.
+ *
+ * @since 1.3
+ */
+public class SpecCompliantAccessTimestampEvaluator implements AccessTimestampEvaluator {
+
+    /**
+     * Servlet Specification-compliant implementation that always returns {@code true}.
+     */
+    public boolean isUpdateAccessTimestamp(BeginServletRequestEvent event) {
+        return true;
+    }
+}