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/05/05 07:31:36 UTC

svn commit: r1334318 - in /shiro/branches/SHIRO-317: core/src/main/java/org/apache/shiro/mgt/ core/src/main/java/org/apache/shiro/session/mgt/ core/src/main/java/org/apache/shiro/subject/ core/src/main/java/org/apache/shiro/subject/support/ support/spr...

Author: lhazlewood
Date: Sat May  5 05:31:35 2012
New Revision: 1334318

URL: http://svn.apache.org/viewvc?rev=1334318&view=rev
Log:
Initial implementation of SHIRO-317 complete.

Added:
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/Flushable.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/FlushableSessionManager.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/UpdateDeferrable.java
Modified:
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/Subject.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/SubjectContext.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java
    shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java
    shiro/branches/SHIRO-317/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java
    shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
    shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java
    shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
    shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
    shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java Sat May  5 05:31:35 2012
@@ -459,9 +459,13 @@ public class DefaultSecurityManager exte
     }
 
     protected SessionKey getSessionKey(SubjectContext context) {
+        DefaultSessionKey sessionKey = null;
         Serializable sessionId = context.getSessionId();
         if (sessionId != null) {
-            return new DefaultSessionKey(sessionId);
+            sessionKey = new DefaultSessionKey(sessionId);
+        }
+        if (sessionKey != null) {
+            sessionKey.setUpdateDeferred(context.isSessionUpdateDeferred());
         }
         return null;
     }

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java Sat May  5 05:31:35 2012
@@ -54,7 +54,10 @@ public abstract class AbstractNativeSess
 
     public Session start(SessionContext context) {
         Session session = createSession(context);
-        applyGlobalSessionTimeout(session);
+        session.setTimeout(getGlobalSessionTimeout()); //todo this configuration in Session creation
+        if (isUpdateImmediate(context)) {
+            onChange(session);
+        }
         onStart(session, context);
         notifyStart(session);
         //Don't expose the EIS-tier Session object to the client-tier:
@@ -77,11 +80,6 @@ public abstract class AbstractNativeSess
      */
     protected abstract Session createSession(SessionContext context) throws AuthorizationException;
 
-    protected void applyGlobalSessionTimeout(Session session) {
-        session.setTimeout(getGlobalSessionTimeout());
-        onChange(session);
-    }
-
     /**
      * Template method that allows subclasses to react to a new session being created.
      * <p/>
@@ -117,11 +115,45 @@ public abstract class AbstractNativeSess
     protected abstract Session doGetSession(SessionKey key) throws InvalidSessionException;
 
     protected Session createExposedSession(Session session, SessionContext context) {
-        return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
+        SessionKey sessionKey = createSessionKey(session, context);
+        return new DelegatingSession(this, sessionKey);
+    }
+
+    //since 1.3
+    protected SessionKey createSessionKey(Session session, SessionContext context) {
+        SessionKey key = doCreateSessionKey(session, context);
+        if (key instanceof UpdateDeferrable && context instanceof UpdateDeferrable) {
+            UpdateDeferrable udKey = (UpdateDeferrable)key;
+            UpdateDeferrable udContext = (UpdateDeferrable)context;
+            udKey.setUpdateDeferred(udContext.isUpdateDeferred());
+        }
+        return key;
+    }
+
+    //since 1.3
+    protected SessionKey createSessionKey(Session session, SessionKey oldKey) {
+        SessionKey newKey = doCreateSessionKey(session, oldKey);
+        if (newKey instanceof UpdateDeferrable && oldKey instanceof UpdateDeferrable) {
+            UpdateDeferrable newKeyUdDeferrable = (UpdateDeferrable)newKey;
+            UpdateDeferrable oldKeyUdDeferrable = (UpdateDeferrable)oldKey;
+            newKeyUdDeferrable.setUpdateDeferred(oldKeyUdDeferrable.isUpdateDeferred());
+        }
+        return newKey;
+    }
+
+    //since 1.3
+    protected SessionKey doCreateSessionKey(Session session, SessionContext context) {
+        return new DefaultSessionKey(session.getId());
+    }
+
+    //since 1.3
+    protected SessionKey doCreateSessionKey(Session session, SessionKey sessionKey) {
+        return new DefaultSessionKey(session.getId());
     }
 
     protected Session createExposedSession(Session session, SessionKey key) {
-        return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
+        SessionKey sessionKey = createSessionKey(session, key);
+        return new DelegatingSession(this, sessionKey);
     }
 
     /**
@@ -181,13 +213,17 @@ public abstract class AbstractNativeSess
     public void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException {
         Session s = lookupRequiredSession(key);
         s.setTimeout(maxIdleTimeInMillis);
-        onChange(s);
+        if (isUpdateImmediate(key)) {
+            onChange(s);
+        }
     }
 
     public void touch(SessionKey key) throws InvalidSessionException {
         Session s = lookupRequiredSession(key);
         s.touch();
-        onChange(s);
+        if (isUpdateImmediate(key)) {
+            onChange(s);
+        }
     }
 
     public String getHost(SessionKey key) {
@@ -212,14 +248,26 @@ public abstract class AbstractNativeSess
         } else {
             Session s = lookupRequiredSession(sessionKey);
             s.setAttribute(attributeKey, value);
-            onChange(s);
+            if (isUpdateImmediate(sessionKey)) {
+                onChange(s);
+            }
         }
     }
 
+    //since 1.3
+    protected final boolean isUpdateDeferred(Object object) {
+        return object instanceof UpdateDeferrable && ((UpdateDeferrable)object).isUpdateDeferred();
+    }
+
+    //since 1.3
+    protected final boolean isUpdateImmediate(Object object) {
+        return !isUpdateDeferred(object);
+    }
+
     public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
         Session s = lookupRequiredSession(sessionKey);
         Object removed = s.removeAttribute(attributeKey);
-        if (removed != null) {
+        if (removed != null && isUpdateImmediate(sessionKey)) {
             onChange(s);
         }
         return removed;

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java Sat May  5 05:31:35 2012
@@ -30,12 +30,13 @@ import java.util.Map;
  *
  * @since 1.0
  */
-public class DefaultSessionContext extends MapContext implements SessionContext {
+public class DefaultSessionContext extends MapContext implements SessionContext, UpdateDeferrable {
 
     private static final long serialVersionUID = -1424160751361252966L;
 
     private static final String HOST = DefaultSessionContext.class.getName() + ".HOST";
     private static final String SESSION_ID = DefaultSessionContext.class.getName() + ".SESSION_ID";
+    private static final String UPDATE_DEFERRED = DefaultSessionContext.class.getName() + ".UPDATE_DEFERRED";
 
     public DefaultSessionContext() {
         super();
@@ -62,4 +63,14 @@ public class DefaultSessionContext exten
     public void setSessionId(Serializable sessionId) {
         nullSafePut(SESSION_ID, sessionId);
     }
+
+    public boolean isUpdateDeferred() {
+        Boolean bool = getTypedValue(UPDATE_DEFERRED, Boolean.class);
+        //noinspection UnnecessaryUnboxing
+        return bool != null && bool.booleanValue();
+    }
+
+    public void setUpdateDeferred(boolean deferred) {
+        nullSafePut(UPDATE_DEFERRED, deferred);
+    }
 }

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java Sat May  5 05:31:35 2012
@@ -24,10 +24,12 @@ import java.io.Serializable;
  *
  * @since 1.0
  */
-public class DefaultSessionKey implements SessionKey, Serializable {
+public class DefaultSessionKey implements SessionKey, Serializable, UpdateDeferrable {
 
     private Serializable sessionId;
 
+    private boolean updateDeferred;
+
     public DefaultSessionKey() {
     }
 
@@ -42,4 +44,12 @@ public class DefaultSessionKey implement
     public Serializable getSessionId() {
         return this.sessionId;
     }
+
+    public boolean isUpdateDeferred() {
+        return this.updateDeferred;
+    }
+
+    public void setUpdateDeferred(boolean deferred) {
+        this.updateDeferred = deferred;
+    }
 }

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java Sat May  5 05:31:35 2012
@@ -20,10 +20,12 @@ package org.apache.shiro.session.mgt;
 
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.session.InvalidSessionException;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.UnknownSessionException;
 import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
 import org.apache.shiro.session.mgt.eis.SessionDAO;
+import org.apache.shiro.util.ThreadContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,12 +40,15 @@ import java.util.Date;
  *
  * @since 0.1
  */
-public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
+public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware, FlushableSessionManager {
 
     //TODO - complete JavaDoc
 
     private static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);
 
+    //since 1.3
+    private static final String SESSION_THREAD_CACHE_KEY = DefaultSessionManager.class.getName() + ".SESSION_THREAD_CACHE_KEY";
+
     private SessionFactory sessionFactory;
 
     protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?
@@ -156,6 +161,9 @@ public class DefaultSessionManager exten
             log.trace("Creating session for host {}", s.getHost());
         }
         create(s);
+        if (isUpdateDeferred(context)) {
+            addToFirstLevelCache(s); //since 1.3
+        }
         return s;
     }
 
@@ -192,6 +200,7 @@ public class DefaultSessionManager exten
         if (isDeleteInvalidSessions()) {
             delete(session);
         }
+        removeSessionFromFirstLevelCache(); //since 1.3
     }
 
     protected void onExpiration(Session session) {
@@ -206,9 +215,11 @@ public class DefaultSessionManager exten
         if (isDeleteInvalidSessions()) {
             delete(session);
         }
+        removeSessionFromFirstLevelCache(); //since 1.3
     }
 
     protected void onChange(Session session) {
+        log.trace("Updating session {}", session);
         sessionDAO.update(session);
     }
 
@@ -219,7 +230,22 @@ public class DefaultSessionManager exten
                     "session could not be found.", sessionKey);
             return null;
         }
-        Session s = retrieveSessionFromDataSource(sessionId);
+
+        Session s = null;
+
+        boolean updateDeferred = false;
+        if (sessionKey instanceof UpdateDeferrable && ((UpdateDeferrable)sessionKey).isUpdateDeferred()) {
+            updateDeferred = true;
+        }
+        if (updateDeferred) {
+            s = retrieveSessionFromFirstLevelCache(sessionId);
+        }
+        if (s == null) {
+            s = retrieveSessionFromDataSource(sessionId);
+            if (s != null && updateDeferred) {
+                addToFirstLevelCache(s);
+            }
+        }
         if (s == 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 + "]";
@@ -232,7 +258,35 @@ public class DefaultSessionManager exten
         return sessionKey.getSessionId();
     }
 
+    //since 1.3
+    protected final void addToFirstLevelCache(Session session) {
+        ThreadContext.put(SESSION_THREAD_CACHE_KEY, session);
+    }
+
+    //since 1.3
+    protected final Session retrieveSessionFromFirstLevelCache(Serializable sessionId) {
+        Session session = (Session) ThreadContext.get(SESSION_THREAD_CACHE_KEY);
+        if (session != null && sessionId.equals(session.getId())) {
+            return session;
+        }
+        return null;
+    }
+
+    protected final void removeSessionFromFirstLevelCache() {
+        ThreadContext.remove(SESSION_THREAD_CACHE_KEY);
+    }
+
+    public void flush(SessionKey key) throws InvalidSessionException {
+        Serializable sessionId = getSessionId(key);
+        Session session = retrieveSessionFromFirstLevelCache(sessionId);
+        if (session != null) {
+            log.trace("Flushing session to data store.  Session id: {}", session.getId());
+            sessionDAO.update(session);
+        }
+    }
+
     protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
+        log.trace("Retrieving session from data source. Session id: {}", sessionId);
         return sessionDAO.readSession(sessionId);
     }
 
@@ -244,5 +298,4 @@ public class DefaultSessionManager exten
         Collection<Session> active = sessionDAO.getActiveSessions();
         return active != null ? active : Collections.<Session>emptySet();
     }
-
 }

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java Sat May  5 05:31:35 2012
@@ -31,16 +31,16 @@ import java.util.Date;
  * This implementation is basically a proxy to a server-side {@link NativeSessionManager NativeSessionManager},
  * which will return the proper results for each method call.
  * <p/>
- * <p>A <tt>DelegatingSession</tt> will cache data when appropriate to avoid a remote method invocation,
+ * A <tt>DelegatingSession</tt> will cache data when appropriate to avoid a remote method invocation,
  * only communicating with the server when necessary.
  * <p/>
- * <p>Of course, if used in-process with a NativeSessionManager business POJO, as might be the case in a
+ * Of course, if used in-process with a NativeSessionManager business POJO, as might be the case in a
  * web-based application where the web classes and server-side business pojos exist in the same
  * JVM, a remote method call will not be incurred.
  *
  * @since 0.1
  */
-public class DelegatingSession implements Session, Serializable {
+public class DelegatingSession implements Session, Flushable, Serializable {
 
     //TODO - complete JavaDoc
 
@@ -158,4 +158,17 @@ public class DelegatingSession implement
     public Object removeAttribute(Object attributeKey) throws InvalidSessionException {
         return sessionManager.removeAttribute(this.key, attributeKey);
     }
+
+    /**
+     * If the backing SessionManager is a {@link FlushableSessionManager}, this method calls
+     * sessionManager.{@link FlushableSessionManager#flush(SessionKey) flush(sessionKey)}, otherwise this implementation
+     * does nothing.
+     *
+     * @since 1.3
+     */
+    public void flush() {
+        if (sessionManager instanceof FlushableSessionManager) {
+            ((FlushableSessionManager) sessionManager).flush(this.key);
+        }
+    }
 }

Added: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/Flushable.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/Flushable.java?rev=1334318&view=auto
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/Flushable.java (added)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/Flushable.java Sat May  5 05:31:35 2012
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+/**
+ * This is a Shiro implementation-specific interface and should probably not be called by Shiro end-users directly.
+ * This interface and its implementations are subject to change without notice.
+ * <p/>
+ * The Flushable interface allows a session to 'flush' its changes with an underlying SessionManager.  It is used
+ * in caching strategies so the underlying session data store is not accessed too frequently during a request.
+ *
+ * @since 1.3
+ */
+public interface Flushable {
+
+    void flush();
+}

Added: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/FlushableSessionManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/FlushableSessionManager.java?rev=1334318&view=auto
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/FlushableSessionManager.java (added)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/FlushableSessionManager.java Sat May  5 05:31:35 2012
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.InvalidSessionException;
+
+/**
+ * @since 1.3
+ */
+public interface FlushableSessionManager extends NativeSessionManager {
+
+    /**
+     * Persists any unsaved Session state to the underlying Session data store.
+     *
+     * @param key the session key to use to look up the target session.
+     * @throws InvalidSessionException if the session id is invalid (it does not exist or it is stopped or expired).
+     */
+    void flush(SessionKey key) throws InvalidSessionException;
+
+}

Added: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/UpdateDeferrable.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/UpdateDeferrable.java?rev=1334318&view=auto
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/UpdateDeferrable.java (added)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/session/mgt/UpdateDeferrable.java Sat May  5 05:31:35 2012
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+/**
+ * @since 1.3
+ */
+public interface UpdateDeferrable {
+
+    /**
+     * Returns {@code true} if modifications that require persistence (updates) can be deferred until a future time,
+     * for example, at the end of a request or method invocation.  Returns {@code false} if update operations must
+     * be relayed immediately to the backing data store as they occur.
+     * <p/>
+     * This interface corresponds only to updates; create and delete operations will always be immediately relayed
+     * to the underlying data store.
+     * <p/>
+     * A {@code true} value reduces the number of times the backing data store (such as a cache or database) must be
+     * accessed during particular scope (e.g. request or thread).  A {@code false} value indicates that the backing
+     * data store will handle updates immediately, probably because it has its own caching mechanism in place to limit
+     * data store round trips.  For example, if the data store had a built-in first level cache with a pluggable
+     * second-level cache).
+     * <p/>
+     * For Shiro 1.3 and later, the default value is {@code true}.  Setting this to {@code false} reverts to Shiro
+     * 1.2 and earlier behavior, but expects the backing datastore to handle many reads/writes during a single
+     * request or invocation.
+     *
+     * @return {@code true} if state modifications during the course of a request or invocation can be persisted at the
+     *         end of a request/invocation, or {@code false} if session modifications must be immediately relayed to
+     *         backing data store as they occur.
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-317">SHIRO-317</a>
+     * @since 1.3
+     */
+    boolean isUpdateDeferred();
+
+    void setUpdateDeferred(boolean deferred);
+}

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/Subject.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/Subject.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/Subject.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/Subject.java Sat May  5 05:31:35 2012
@@ -52,7 +52,7 @@ import java.util.concurrent.Callable;
  * simply convert these String values to {@link Permission Permission} instances and then just call the corresponding
  * type-safe method.  (Shiro's default implementations do String-to-Permission conversion for these methods using
  * {@link org.apache.shiro.authz.permission.PermissionResolver PermissionResolver}s.)
- * <p/>
+ *
  * These overloaded *Permission methods forgo type-saftey for the benefit of convenience and simplicity,
  * so you should choose which ones to use based on your preferences and needs.
  *
@@ -309,7 +309,7 @@ public interface Subject {
      *
      * @param roleIdentifiers roleIdentifiers the application-specific role identifiers to check (usually role ids or role names).
      * @throws AuthorizationException org.apache.shiro.authz.AuthorizationException
-     *          if this Subject does not have all of the specified roles.
+     *                                if this Subject does not have all of the specified roles.
      * @since 1.1.0
      */
     void checkRoles(String... roleIdentifiers) throws AuthorizationException;
@@ -356,13 +356,13 @@ public interface Subject {
      * this method act as the logical equivalent to this code:
      * <pre>
      * {@link #getPrincipal() getPrincipal()} != null && !{@link #isAuthenticated() isAuthenticated()}</pre>
-     * <p/>
+     *
      * Note as indicated by the above code example, if a {@code Subject} is remembered, they are
      * <em>NOT</em> considered authenticated.  A check against {@link #isAuthenticated() isAuthenticated()} is a more
      * strict check than that reflected by this method.  For example, a check to see if a subject can access financial
      * information should almost always depend on {@link #isAuthenticated() isAuthenticated()} to <em>guarantee</em> a
      * verified identity, and not this method.
-     * <p/>
+     *
      * Once the subject is authenticated, they are no longer considered only remembered because their identity would
      * have been verified during the current session.
      * <h4>Remembered vs Authenticated</h4>
@@ -370,23 +370,23 @@ public interface Subject {
      * the remembered identity gives the system an idea who that user probably is, but in reality, has no way of
      * absolutely <em>guaranteeing</em> if the remembered {@code Subject} represents the user currently
      * using the application.
-     * <p/>
+     *
      * So although many parts of the application can still perform user-specific logic based on the remembered
      * {@link #getPrincipals() principals}, such as customized views, it should never perform highly-sensitive
      * operations until the user has legitimately verified their identity by executing a successful authentication
      * attempt.
-     * <p/>
+     *
      * We see this paradigm all over the web, and we will use <a href="http://www.amazon.com">Amazon.com</a> as an
      * example:
-     * <p/>
+     *
      * When you visit Amazon.com and perform a login and ask it to 'remember me', it will set a cookie with your
      * identity.  If you don't log out and your session expires, and you come back, say the next day, Amazon still knows
      * who you <em>probably</em> are: you still see all of your book and movie recommendations and similar user-specific
      * features since these are based on your (remembered) user id.
-     * <p/>
+     *
      * BUT, if you try to do something sensitive, such as access your account's billing data, Amazon forces you
      * to do an actual log-in, requiring your username and password.
-     * <p/>
+     *
      * This is because although amazon.com assumed your identity from 'remember me', it recognized that you were not
      * actually authenticated.  The only way to really guarantee you are who you say you are, and therefore allow you
      * access to sensitive account data, is to force you to perform an actual successful authentication.  You can
@@ -585,18 +585,18 @@ public interface Subject {
      * specify the exact {@code SecurityManager} instance to be used by the additional
      * <code>Subject.{@link #Builder(org.apache.shiro.mgt.SecurityManager) Builder(securityManager)}</code>
      * constructor if desired.
-     * <p/>
+     *
      * All other methods may be called before the {@link #buildSubject() buildSubject()} method to
      * provide context on how to construct the {@code Subject} instance.  For example, if you have a session id and
      * want to acquire the {@code Subject} that 'owns' that session (assuming the session exists and is not expired):
      * <pre>
      * Subject subject = new Subject.Builder().sessionId(sessionId).buildSubject();</pre>
-     * <p/>
+     *
      * Similarly, if you want a {@code Subject} instance reflecting a certain identity:
      * <pre>
      * PrincipalCollection principals = new SimplePrincipalCollection("username", <em>yourRealmName</em>);
      * Subject subject = new Subject.Builder().principals(principals).build();</pre>
-     * <p/>
+     *
      * <b>Note*</b> that the returned {@code Subject} instance is <b>not</b> automatically bound to the application (thread)
      * for further use.  That is,
      * {@link org.apache.shiro.SecurityUtils SecurityUtils}.{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}
@@ -675,15 +675,15 @@ public interface Subject {
          * session alone.  In other words, this is almost always sufficient:
          * <pre>
          * new Subject.Builder().sessionId(sessionId).buildSubject();</pre>
-         * <p/>
+         *
          * <b>Although simple in concept, this method provides very powerful functionality previously absent in almost
          * all Java environments:</b>
-         * <p/>
+         *
          * The ability to reference a {@code Subject} and their server-side session
          * <em>across clients of different mediums</em> such as web applications, Java applets,
          * standalone C# clients over XML-RPC and/or SOAP, and many others. This is a <em>huge</em>
          * benefit in heterogeneous enterprise applications.
-         * <p/>
+         *
          * To maintain session integrity across client mediums, the {@code sessionId} <b>must</b> be transmitted
          * to all client mediums securely (e.g. over SSL) to prevent man-in-the-middle attacks.  This
          * is nothing new - all web applications are susceptible to the same problem when transmitting
@@ -740,7 +740,7 @@ public interface Subject {
          * <pre>
          * PrincipalCollection identity = new {@link org.apache.shiro.subject.SimplePrincipalCollection#SimplePrincipalCollection(Object, String) SimplePrincipalCollection}(&quot;jsmith&quot;, &quot;myRealm&quot;);
          * Subject jsmith = new Subject.Builder().principals(identity).buildSubject();</pre>
-         * <p/>
+         *
          * Similarly, if your application's unique identifier for users is a {@code long} value (such as might be used
          * as a primary key in a relational database) and you were using a {@code JDBC}
          * {@code Realm} named, (unimaginatively) &quot;jdbcRealm&quot;, you might create the Subject
@@ -794,6 +794,43 @@ public interface Subject {
         }
 
         /**
+         * Sets whether or not any session associated with the built subject will have state changes deferred until
+         * a caller can explicitly {@code flush()} the session.
+         * <p/>
+         * Setting this to be true is a performance enhancement, typically used during web requests and/or invocation
+         * chains but <b>REQUIRES THE FOLLOWING</b>:
+         * <p/>
+         * The caller building the subject <em>MUST</em> {@link org.apache.shiro.session.mgt.Flushable#flush() flush} any
+         * session modifications at the end of the request/invocation chain.  For example:
+         * <pre>
+         *     final Subject subject = new Subject.Builder(...).sessionUpdateDeferred(true).... .buildSubject();
+         *
+         *     try {
+         *         subject.execute(...);
+         *     } finally {
+         *         Session session = subject.getSession(false);
+         *         if (session instanceof Flushable) {
+         *             ((Flushable)session).flush();
+         *         }
+         *     }
+         * </pre>
+         * <p/>
+         * The flush() call MUST be called to persist any session state changes during the execution or the session
+         * will never be updated.
+         *
+         * @param deferred {@code true} to defer session updates until flush may be called, {@code false} to ensure
+         *                 updates are persisted as they occur.
+         * @return whether or not any session associated with the built subject will have state changes deferred until
+         *         a caller can explicitly {@code flush()} the session.
+         * @see org.apache.shiro.session.mgt.UpdateDeferrable#isUpdateDeferred()
+         * @since 1.3
+         */
+        public Builder sessionUpdateDeferred(boolean deferred) {
+            this.subjectContext.setSessionUpdateDeferred(deferred);
+            return this;
+        }
+
+        /**
          * Allows custom attributes to be added to the underlying context {@code Map} used to construct the
          * {@link Subject} instance.
          * <p/>
@@ -846,5 +883,4 @@ public interface Subject {
             return this.securityManager.createSubject(this.subjectContext);
         }
     }
-
 }

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/SubjectContext.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/SubjectContext.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/SubjectContext.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/SubjectContext.java Sat May  5 05:31:35 2012
@@ -234,4 +234,16 @@ public interface SubjectContext extends 
     void setHost(String host);
 
     String resolveHost();
+
+    /**
+     * @since 1.3
+     * @see org.apache.shiro.session.mgt.UpdateDeferrable UpdateDeferrable
+     */
+    boolean isSessionUpdateDeferred();
+
+    /**
+     * @since 1.3
+     * @see org.apache.shiro.session.mgt.UpdateDeferrable UpdateDeferrable
+     */
+    void setSessionUpdateDeferred(boolean deferred);
 }

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java Sat May  5 05:31:35 2012
@@ -77,6 +77,8 @@ public class DefaultSubjectContext exten
      */
     public static final String AUTHENTICATED_SESSION_KEY = DefaultSubjectContext.class.getName() + "_AUTHENTICATED_SESSION_KEY";
 
+    public static final String SESSION_UPDATE_DEFERRED = DefaultSubjectContext.class.getName() + ".SESSION_UPDATE_DEFERRED";
+
     private static final transient Logger log = LoggerFactory.getLogger(DefaultSubjectContext.class);
 
     public DefaultSubjectContext() {
@@ -273,4 +275,13 @@ public class DefaultSubjectContext exten
 
         return host;
     }
+
+    public boolean isSessionUpdateDeferred() {
+        Boolean bool = getTypedValue(SESSION_UPDATE_DEFERRED, Boolean.class);
+        return bool != null && bool;
+    }
+
+    public void setSessionUpdateDeferred(boolean deferred) {
+        nullSafePut(SESSION_UPDATE_DEFERRED, deferred);
+    }
 }

Modified: shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java (original)
+++ shiro/branches/SHIRO-317/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java Sat May  5 05:31:35 2012
@@ -30,6 +30,7 @@ import org.apache.shiro.session.ProxiedS
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.SessionException;
 import org.apache.shiro.session.mgt.DefaultSessionContext;
+import org.apache.shiro.session.mgt.Flushable;
 import org.apache.shiro.session.mgt.SessionContext;
 import org.apache.shiro.subject.ExecutionException;
 import org.apache.shiro.subject.PrincipalCollection;
@@ -79,10 +80,9 @@ public class DelegatingSubject implement
     protected boolean authenticated;
     protected String host;
     protected Session session;
-    /**
-     * @since 1.2
-     */
-    protected boolean sessionCreationEnabled;
+    protected boolean sessionCreationEnabled; //since 1.2
+    protected boolean sessionUpdateDeferred; //since 1.3
+
     private List<PrincipalCollection> runAsPrincipals; //supports assumed identities (aka 'run as')
 
     protected transient SecurityManager securityManager;
@@ -99,6 +99,13 @@ public class DelegatingSubject implement
     //since 1.2
     public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
                              Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
+        this(principals, authenticated, host, session, sessionCreationEnabled, false, securityManager);
+    }
+
+    //since 1.3
+    public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
+                             Session session, boolean sessionCreationEnabled, boolean sessionUpdateDeferred,
+                             SecurityManager securityManager) {
         if (securityManager == null) {
             throw new IllegalArgumentException("SecurityManager argument cannot be null.");
         }
@@ -111,13 +118,18 @@ public class DelegatingSubject implement
             this.runAsPrincipals = getRunAsPrincipals(this.session);
         }
         this.sessionCreationEnabled = sessionCreationEnabled;
+        this.sessionUpdateDeferred = sessionUpdateDeferred;
     }
 
     protected Session decorate(Session session) {
         if (session == null) {
             throw new IllegalArgumentException("session cannot be null");
         }
-        return new StoppingAwareProxiedSession(session, this);
+        if (session instanceof Flushable) {
+            return new FlushableStoppingAwareProxiedSession(session, this);
+        } else {
+            return new StoppingAwareProxiedSession(session, this);
+        }
     }
 
     public SecurityManager getSecurityManager() {
@@ -310,6 +322,10 @@ public class DelegatingSubject implement
         return this.sessionCreationEnabled;
     }
 
+    protected boolean isSessionUpdateDeferred() {
+        return this.sessionUpdateDeferred;
+    }
+
     public Session getSession() {
         return getSession(true);
     }
@@ -340,10 +356,14 @@ public class DelegatingSubject implement
     }
 
     protected SessionContext createSessionContext() {
-        SessionContext sessionContext = new DefaultSessionContext();
+        DefaultSessionContext sessionContext = new DefaultSessionContext();
         if (StringUtils.hasText(host)) {
             sessionContext.setHost(host);
         }
+        //added for 1.3 (see SHIRO-317):
+        if (isSessionUpdateDeferred()) {
+            sessionContext.setUpdateDeferred(isSessionUpdateDeferred());
+        }
         return sessionContext;
     }
 
@@ -418,6 +438,27 @@ public class DelegatingSubject implement
         }
     }
 
+    private class FlushableStoppingAwareProxiedSession extends StoppingAwareProxiedSession implements Flushable {
+
+        private final DelegatingSubject owner;
+
+        private FlushableStoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
+            super(target, owningSubject);
+            if (!(target instanceof Flushable)) {
+                throw new IllegalStateException("Target session must be Flushable");
+            }
+            owner = owningSubject;
+        }
+
+        public void stop() throws InvalidSessionException {
+            super.stop();
+            owner.sessionStopped();
+        }
+
+        public void flush() {
+            ((Flushable)delegate).flush();
+        }
+    }
 
     // ======================================
     // 'Run As' support implementations

Modified: shiro/branches/SHIRO-317/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java (original)
+++ shiro/branches/SHIRO-317/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java Sat May  5 05:31:35 2012
@@ -20,8 +20,11 @@ package org.apache.shiro.spring.remoting
 
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.Flushable;
 import org.apache.shiro.subject.ExecutionException;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
@@ -59,6 +62,8 @@ public class SecureRemoteInvocationExecu
      */
     private SecurityManager securityManager;
 
+    private boolean isSessionUpdateDeferred = true; //since 1.3
+
     /*--------------------------------------------
     |         C O N S T R U C T O R S           |
     ============================================*/
@@ -71,6 +76,18 @@ public class SecureRemoteInvocationExecu
         this.securityManager = securityManager;
     }
 
+    public boolean isSessionUpdateDeferred() {
+        return isSessionUpdateDeferred;
+    }
+
+    public void setSessionUpdateDeferred(boolean sessionUpdateDeferred) {
+        isSessionUpdateDeferred = sessionUpdateDeferred;
+    }
+
+    protected boolean isSessionUpdateDeferred(RemoteInvocation ri, Object targetObject) {
+        return isSessionUpdateDeferred();
+    }
+
     /*--------------------------------------------
     |               M E T H O D S               |
     ============================================*/
@@ -100,12 +117,29 @@ public class SecureRemoteInvocationExecu
                 }
             }
 
+            boolean sessionUpdateDeferred = isSessionUpdateDeferred(invocation, targetObject);
+            builder.sessionUpdateDeferred(sessionUpdateDeferred);
+
             Subject subject = builder.buildSubject();
-            return subject.execute(new Callable() {
-                public Object call() throws Exception {
-                    return SecureRemoteInvocationExecutor.super.invoke(invocation, targetObject);
+
+            try {
+                return subject.execute(new Callable() {
+                    public Object call() throws Exception {
+                        return SecureRemoteInvocationExecutor.super.invoke(invocation, targetObject);
+                    }
+                });
+            } finally {
+                try {
+                    if (sessionUpdateDeferred) {
+                        Session session = subject.getSession(false);
+                        if (session instanceof Flushable) {
+                            ((Flushable)session).flush();
+                        }
+                    }
+                } finally {
+                    ThreadContext.remove();
                 }
-            });
+            }
         } catch (ExecutionException e) {
             Throwable cause = e.getCause();
             if (cause instanceof NoSuchMethodException) {

Modified: shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java (original)
+++ shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java Sat May  5 05:31:35 2012
@@ -201,10 +201,11 @@ public class DefaultWebSecurityManager e
             Serializable sessionId = context.getSessionId();
             ServletRequest request = WebUtils.getRequest(context);
             ServletResponse response = WebUtils.getResponse(context);
-            return new WebSessionKey(sessionId, request, response);
+            WebSessionKey wsk = new WebSessionKey(sessionId, request, response);
+            wsk.setUpdateDeferred(context.isSessionUpdateDeferred());
+            return wsk;
         } else {
             return super.getSessionKey(context);
-
         }
     }
 

Modified: shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java (original)
+++ shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java Sat May  5 05:31:35 2012
@@ -53,13 +53,14 @@ public class DefaultWebSubjectFactory ex
         SecurityManager securityManager = wsc.resolveSecurityManager();
         Session session = wsc.resolveSession();
         boolean sessionEnabled = wsc.isSessionCreationEnabled();
+        boolean sessionUpdateDeferred = wsc.isSessionUpdateDeferred();
         PrincipalCollection principals = wsc.resolvePrincipals();
         boolean authenticated = wsc.resolveAuthenticated();
         String host = wsc.resolveHost();
         ServletRequest request = wsc.resolveServletRequest();
         ServletResponse response = wsc.resolveServletResponse();
 
-        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
+        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, sessionUpdateDeferred,
                 request, response, securityManager);
     }
 
@@ -72,7 +73,7 @@ public class DefaultWebSubjectFactory ex
                                          String host, Session session,
                                          ServletRequest request, ServletResponse response,
                                          SecurityManager securityManager) {
-        return new WebDelegatingSubject(principals, authenticated, host, session, true,
+        return new WebDelegatingSubject(principals, authenticated, host, session, true, false,
                 request, response, securityManager);
     }
 }

Modified: shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java (original)
+++ shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java Sat May  5 05:31:35 2012
@@ -20,8 +20,10 @@ package org.apache.shiro.web.servlet;
 
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.Flushable;
 import org.apache.shiro.subject.ExecutionException;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 import org.apache.shiro.web.mgt.WebSecurityManager;
@@ -289,7 +291,9 @@ public abstract class AbstractShiroFilte
      * @since 1.0
      */
     protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
-        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
+        WebSubject.Builder builder = new WebSubject.Builder(getSecurityManager(), request, response);
+        builder.sessionUpdateDeferred(true); //added in 1.3 - MUST be accompanied by finally 'flush'.
+        return builder.buildWebSubject();
     }
 
     /**
@@ -358,14 +362,26 @@ public abstract class AbstractShiroFilte
 
             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;
-                }
-            });
+            try {
+                //noinspection unchecked
+                subject.execute(new Callable() {
+                    public Object call() throws Exception {
+                        try {
+                            updateSessionLastAccessTime(request, response);
+                            executeChain(request, response, chain);
+                            return null;
+                        } finally {
+                            Session session = subject.getSession(false);
+                            if (session instanceof Flushable) {
+                                ((Flushable) session).flush();
+                            }
+                        }
+                    }
+                });
+            } finally {
+                ThreadContext.remove(); //silence innocuous Tomcat ThreadLocal warnings
+            }
+
         } catch (ExecutionException ex) {
             t = ex.getCause();
         } catch (Throwable throwable) {

Modified: shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java (original)
+++ shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java Sat May  5 05:31:35 2012
@@ -21,14 +21,12 @@ package org.apache.shiro.web.session.mgt
 import org.apache.shiro.session.ExpiredSessionException;
 import org.apache.shiro.session.InvalidSessionException;
 import org.apache.shiro.session.Session;
-import org.apache.shiro.session.mgt.DefaultSessionManager;
-import org.apache.shiro.session.mgt.DelegatingSession;
-import org.apache.shiro.session.mgt.SessionContext;
-import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.session.mgt.*;
 import org.apache.shiro.web.servlet.Cookie;
 import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
 import org.apache.shiro.web.servlet.ShiroHttpSession;
 import org.apache.shiro.web.servlet.SimpleCookie;
+import org.apache.shiro.web.util.RequestPairSource;
 import org.apache.shiro.web.util.WebUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -134,25 +132,26 @@ public class DefaultWebSessionManager ex
         return id;
     }
 
-    protected Session createExposedSession(Session session, SessionContext context) {
+    @Override
+    protected SessionKey doCreateSessionKey(Session session, SessionContext context) {
         if (!WebUtils.isWeb(context)) {
-            return super.createExposedSession(session, context);
+            return super.doCreateSessionKey(session, context);
         }
-        ServletRequest request = WebUtils.getRequest(context);
-        ServletResponse response = WebUtils.getResponse(context);
-        SessionKey key = new WebSessionKey(session.getId(), request, response);
-        return new DelegatingSession(this, key);
+        return createSessionKey(session, (RequestPairSource)context);
     }
 
-    protected Session createExposedSession(Session session, SessionKey key) {
+    @Override
+    protected SessionKey doCreateSessionKey(Session session, SessionKey key) {
         if (!WebUtils.isWeb(key)) {
-            return super.createExposedSession(session, key);
+            return super.doCreateSessionKey(session, key);
         }
+        return createSessionKey(session, (RequestPairSource)key);
+    }
 
-        ServletRequest request = WebUtils.getRequest(key);
-        ServletResponse response = WebUtils.getResponse(key);
-        SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
-        return new DelegatingSession(this, sessionKey);
+    protected SessionKey createSessionKey(Session session, RequestPairSource rpSource) {
+        ServletRequest request = WebUtils.getRequest(rpSource);
+        ServletResponse response = WebUtils.getResponse(rpSource);
+        return new WebSessionKey(session.getId(), request, response);
     }
 
     /**

Modified: shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java
URL: http://svn.apache.org/viewvc/shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java?rev=1334318&r1=1334317&r2=1334318&view=diff
==============================================================================
--- shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java (original)
+++ shiro/branches/SHIRO-317/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java Sat May  5 05:31:35 2012
@@ -40,24 +40,32 @@ import javax.servlet.ServletResponse;
  */
 public class WebDelegatingSubject extends DelegatingSubject implements WebSubject {
 
-    private static final long serialVersionUID = -1655724323350159250L;
-
-    private final ServletRequest servletRequest;
-    private final ServletResponse servletResponse;
+    private final transient ServletRequest servletRequest;
+    private final transient ServletResponse servletResponse;
 
+    @SuppressWarnings("UnusedDeclaration") //should keep for backwards compatibility
     public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
                                 String host, Session session,
                                 ServletRequest request, ServletResponse response,
                                 SecurityManager securityManager) {
-        this(principals, authenticated, host, session, true, request, response, securityManager);
+        this(principals, authenticated, host, session, true, false, request, response, securityManager);
     }
 
     //since 1.2
+    @SuppressWarnings("UnusedDeclaration") //should keep for backwards compatibility
     public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
                                 String host, Session session, boolean sessionEnabled,
                                 ServletRequest request, ServletResponse response,
                                 SecurityManager securityManager) {
-        super(principals, authenticated, host, session, sessionEnabled, securityManager);
+        this(principals, authenticated, host, session, sessionEnabled, false, request, response, securityManager);
+    }
+
+    //since 1.3
+    public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
+                                String host, Session session, boolean sessionEnabled, boolean sessionUpdateDeferred,
+                                ServletRequest request, ServletResponse response,
+                                SecurityManager securityManager) {
+        super(principals, authenticated, host, session, sessionEnabled, sessionUpdateDeferred, securityManager);
         this.servletRequest = request;
         this.servletResponse = response;
     }
@@ -91,13 +99,18 @@ public class WebDelegatingSubject extend
 
     @Override
     protected SessionContext createSessionContext() {
-        WebSessionContext wsc = new DefaultWebSessionContext();
+        DefaultWebSessionContext wsc = new DefaultWebSessionContext();
         String host = getHost();
         if (StringUtils.hasText(host)) {
             wsc.setHost(host);
         }
+        //added for 1.3 (see SHIRO-317):
+        if (isSessionUpdateDeferred()) {
+            wsc.setUpdateDeferred(isSessionUpdateDeferred());
+        }
         wsc.setServletRequest(this.servletRequest);
         wsc.setServletResponse(this.servletResponse);
+
         return wsc;
     }
 }