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 2010/03/26 02:41:05 UTC

svn commit: r927664 - in /incubator/shiro/trunk: core/src/main/java/org/apache/shiro/cache/ core/src/main/java/org/apache/shiro/mgt/ core/src/main/java/org/apache/shiro/session/mgt/eis/ samples/spring-client/ samples/spring/

Author: lhazlewood
Date: Fri Mar 26 01:41:04 2010
New Revision: 927664

URL: http://svn.apache.org/viewvc?rev=927664&view=rev
Log:
SHIRO-144: 
- Created AbstractSessionDAO and made CachingSessionDAO an abstract subclass.  
- MemorySessionDAO is now a concrete class of AbstractSessionDAO and does not subclass CachingSessionDAO. 
- The DefaultSessionManager now uses the MemorySessionDAO due to the previous SoftHashMap based default CacheManager losing sessions due to garbage collection.  
- Deprecated HashtableCacheManager and HashtableCache classes have been deleted in preparation for the 1.0 release.

SHIRO-122:
- New AbstractSessionDAO now supports the concept of a SessionIdGenerator.  
- Existing code has been refactored into two new implementations: RandomSessionIdGenerator and JavaUuidSessionIdGenerator.  
- The AbstractSessionDAO now defaults to the JavaUuidSessionIdgGenerator as its default ID generation strategy.

Added:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java
Removed:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/HashtableCache.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/HashtableCacheManager.java
Modified:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MapCache.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
    incubator/shiro/trunk/samples/spring-client/pom.xml
    incubator/shiro/trunk/samples/spring/pom.xml

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java?rev=927664&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java Fri Mar 26 01:41:04 2010
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2008 Les Hazlewood
+ *
+ * Licensed 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.cache;
+
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.StringUtils;
+
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Very simple abstract {@code CacheManager} implementation that retains all created {@link Cache Cache} instances in
+ * an in-memory {@link ConcurrentMap ConcurrentMap}.  {@code Cache} instance creation is left to subclasses via
+ * the {@link #createCache createCache} method implementation.
+ *
+ * @since 1.0
+ */
+public abstract class AbstractCacheManager implements CacheManager, Destroyable {
+
+    /**
+     * Retains all Cache objects maintained by this cache manager.
+     */
+    private final ConcurrentMap<String, Cache> caches;
+
+    /**
+     * Default no-arg constructor that instantiates an internal name-to-cache {@code ConcurrentMap}.
+     */
+    public AbstractCacheManager() {
+        this.caches = new ConcurrentHashMap<String, Cache>();
+    }
+
+    /**
+     * Returns the cache with the specified {@code name}.  If the cache instance does not yet exist, it will be lazily
+     * created, retained for further access, and then returned.
+     *
+     * @param name the name of the cache to acquire.
+     * @return the cache with the specified {@code name}.
+     * @throws IllegalArgumentException if the {@code name} argument is {@code null} or does not contain text.
+     * @throws CacheException           if there is a problem lazily creating a {@code Cache} instance.
+     */
+    public Cache getCache(String name) throws IllegalArgumentException, CacheException {
+        if (!StringUtils.hasText(name)) {
+            throw new IllegalArgumentException("Cache name cannot be null or empty.");
+        }
+
+        Cache cache;
+
+        cache = caches.get(name);
+        if (cache == null) {
+            cache = createCache(name);
+            Cache existing = caches.putIfAbsent(name, cache);
+            if (existing != null) {
+                cache = existing;
+            }
+        }
+
+        return cache;
+    }
+
+    /**
+     * Creates a new {@code Cache} instance associated with the specified {@code name}.
+     *
+     * @param name the name of the cache to create
+     * @return a new {@code Cache} instance associated with the specified {@code name}.
+     * @throws CacheException if the {@code Cache} instance cannot be created.
+     */
+    protected abstract Cache createCache(String name) throws CacheException;
+
+    /**
+     * Cleanup method that first {@link LifecycleUtils#destroy destroys} all of it's managed caches and then
+     * {@link java.util.Map#clear clears} out the internally referenced cache map.
+     *
+     * @throws Exception if any of the managed caches can't destroy properly.
+     */
+    public void destroy() throws Exception {
+        while (!caches.isEmpty()) {
+            for (Cache cache : caches.values()) {
+                LifecycleUtils.destroy(cache);
+            }
+            caches.clear();
+        }
+    }
+
+    public String toString() {
+        Collection<Cache> values = caches.values();
+        StringBuilder sb = new StringBuilder(getClass().getSimpleName())
+                .append(" with ")
+                .append(caches.size())
+                .append(" cache(s)): [");
+        int i = 0;
+        for (Cache cache : values) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(cache.toString());
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MapCache.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MapCache.java?rev=927664&r1=927663&r2=927664&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MapCache.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MapCache.java Fri Mar 26 01:41:04 2010
@@ -95,6 +95,10 @@ public class MapCache implements Cache {
     }
 
     public String toString() {
-        return getClass().getName() + " : [" + name + "]";
+        return new StringBuilder("MapCache '")
+                .append(name).append("' (")
+                .append(map.size())
+                .append(" entries)")
+                .toString();
     }
 }

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java?rev=927664&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java Fri Mar 26 01:41:04 2010
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 Les Hazlewood
+ *
+ * Licensed 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.cache;
+
+import org.apache.shiro.util.SoftHashMap;
+
+/**
+ * Simple memory-only based {@link CacheManager CacheManager} implementation usable in production
+ * environments.  It will not cause memory leaks as it produces {@link Cache Cache}s backed by
+ * {@link SoftHashMap SoftHashMap}s which auto-size themselves based on the runtime environment's memory
+ * limitations and garbage collection behavior.
+ * <p/>
+ * While the {@code Cache} instances created are thread-safe, they do not offer any enterprise-level features such as
+ * cache coherency, optimistic locking, failover or other similar features.  For more enterprise features, consider
+ * using a different {@code CacheManager} implementation backed by an enterprise-grade caching product (EhCache,
+ * TerraCotta, Coherence, GigaSpaces, etc, etc).
+ *
+ * @since 1.0
+ */
+public class MemoryConstrainedCacheManager extends AbstractCacheManager {
+
+    /**
+     * Returns a new {@link MapCache MapCache} instance backed by a {@link SoftHashMap}.
+     *
+     * @param name the name of the cache
+     * @return a new {@link MapCache MapCache} instance backed by a {@link SoftHashMap}.
+     */
+    @Override
+    protected Cache createCache(String name) {
+        return new MapCache(name, new SoftHashMap());
+    }
+}

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java?rev=927664&r1=927663&r2=927664&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java Fri Mar 26 01:41:04 2010
@@ -20,7 +20,7 @@ package org.apache.shiro.mgt;
 
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.cache.CacheManagerAware;
-import org.apache.shiro.cache.DefaultCacheManager;
+import org.apache.shiro.cache.MemoryConstrainedCacheManager;
 import org.apache.shiro.util.Destroyable;
 import org.apache.shiro.util.LifecycleUtils;
 
@@ -48,7 +48,7 @@ public abstract class CachingSecurityMan
      * Default no-arg constructor that will automatically attempt to initialize a default cacheManager
      */
     public CachingSecurityManager() {
-        this.cacheManager = new DefaultCacheManager();
+        this.cacheManager = new MemoryConstrainedCacheManager();
     }
 
     /**
@@ -91,4 +91,5 @@ public abstract class CachingSecurityMan
         LifecycleUtils.destroy(getCacheManager());
         this.cacheManager = null;
     }
+
 }

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java?rev=927664&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java Fri Mar 26 01:41:04 2010
@@ -0,0 +1,186 @@
+/*
+ * 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.eis;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.SimpleSession;
+
+import java.io.Serializable;
+
+
+/**
+ * An abstract {@code SessionDAO} implementation that performs some sanity checks on session creation and reading and
+ * allows for pluggable Session ID generation strategies if desired.  The {@code SessionDAO}
+ * {@link SessionDAO#update update} and {@link SessionDAO#delete delete} methods are left to
+ * subclasses.
+ * <h3>Session ID Generation</h3>
+ * This class also allows for plugging in a {@link SessionIdGenerator} for custom ID generation strategies.  This is
+ * optional, as the default generator is probably sufficient for most cases.  Subclass implementations that do use a
+ * generator (default or custom) will want to call the
+ * {@link #generateSessionId(org.apache.shiro.session.Session)} method from within their {@link #doCreate}
+ * implementations.
+ * <p/>
+ * Subclass implementations that rely on the EIS data store to generate the ID automatically (e.g. when the session
+ * ID is also an auto-generated primary key), they can simply ignore the {@code SessionIdGenerator} concept
+ * entirely and just return the data store's ID from the {@link #doCreate} implementation.
+ *
+ * @author The Apache Shiro Project (shiro-dev@incubator.apache.org)
+ * @since 1.0
+ */
+public abstract class AbstractSessionDAO implements SessionDAO {
+
+    /**
+     * Optional SessionIdGenerator instance available to subclasses via the
+     * {@link #generateSessionId(org.apache.shiro.session.Session)} method.
+     */
+    private SessionIdGenerator sessionIdGenerator;
+
+    /**
+     * Default no-arg constructor that defaults the {@link #setSessionIdGenerator sessionIdGenerator} to be a
+     * {@link org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator}.
+     */
+    public AbstractSessionDAO() {
+        this.sessionIdGenerator = new JavaUuidSessionIdGenerator();
+    }
+
+    /**
+     * Returns the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
+     * method.  Unless overridden by the {@link #setSessionIdGenerator(SessionIdGenerator)} method, the default instance
+     * is a {@link JavaUuidSessionIdGenerator}.
+     *
+     * @return the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
+     *         method.
+     */
+    public SessionIdGenerator getSessionIdGenerator() {
+        return sessionIdGenerator;
+    }
+
+    /**
+     * Sets the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
+     * method.  Unless overridden by this method, the default instance ss a {@link JavaUuidSessionIdGenerator}.
+     *
+     * @param sessionIdGenerator the {@code SessionIdGenerator} to use in the
+     *                           {@link #generateSessionId(org.apache.shiro.session.Session)} method.
+     */
+    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+        this.sessionIdGenerator = sessionIdGenerator;
+    }
+
+    /**
+     * Generates a new ID to be applied to the specified {@code session} instance.  This method is usually called
+     * from within a subclass's {@link #doCreate} implementation where they assign the returned id to the session
+     * instance and then create a record with this ID in the EIS data store.
+     * <p/>
+     * Subclass implementations backed by EIS data stores that auto-generate IDs during record creation, such as
+     * relational databases, don't need to use this method or the {@link #getSessionIdGenerator() sessionIdGenerator}
+     * attribute - they can simply return the data store's generated ID from the {@link #doCreate} implementation
+     * if desired.
+     * <p/>
+     * This implementation uses the {@link #setSessionIdGenerator configured} {@link SessionIdGenerator} to create
+     * the ID.
+     *
+     * @param session the new session instance for which an ID will be generated and then assigned
+     * @return the generated ID to assign
+     */
+    protected Serializable generateSessionId(Session session) {
+        if (this.sessionIdGenerator == null) {
+            String msg = "sessionIdGenerator attribute has not been configured.";
+            throw new IllegalStateException(msg);
+        }
+        return this.sessionIdGenerator.generateId(session);
+    }
+
+    /**
+     * Creates the session by delegating EIS creation to subclasses via the {@link #doCreate} method, and then
+     * asserting that the returned sessionId is not null.
+     *
+     * @param session Session object to create in the EIS and associate with an ID.
+     */
+    public Serializable create(Session session) {
+        Serializable sessionId = doCreate(session);
+        verifySessionId(sessionId);
+        return sessionId;
+    }
+
+    /**
+     * Ensures the sessionId returned from the subclass implementation of {@link #doCreate} is not null and not
+     * already in use.
+     *
+     * @param sessionId session id returned from the subclass implementation of {@link #doCreate}
+     */
+    private void verifySessionId(Serializable sessionId) {
+        if (sessionId == null) {
+            String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    /**
+     * Utility method available to subclasses that wish to
+     * assign a generated session ID to the session instance directly.  This method is not used by the
+     * {@code AbstractSessionDAO} implementation directly, but it is provided so subclasses don't
+     * need to know the {@code Session} implementation if they don't need to.
+     * <p/>
+     * This default implementation casts the argument to a {@link SimpleSession}, Shiro's default EIS implementation.
+     *
+     * @param session   the session instance to which the sessionId will be applied
+     * @param sessionId the id to assign to the specified session instance.
+     */
+    protected void assignSessionId(Session session, Serializable sessionId) {
+        ((SimpleSession) session).setId(sessionId);
+    }
+
+    /**
+     * Subclass hook to actually persist the given <tt>Session</tt> instance to the underlying EIS.
+     *
+     * @param session the Session instance to persist to the EIS.
+     * @return the id of the session created in the EIS (i.e. this is almost always a primary key and should be the
+     *         value returned from {@link org.apache.shiro.session.Session#getId() Session.getId()}.
+     */
+    protected abstract Serializable doCreate(Session session);
+
+    /**
+     * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt> by delegating to
+     * the {@link #doReadSession(java.io.Serializable)} method.  If {@code null} is returned from that method, an
+     * {@link UnknownSessionException} will be thrown.
+     *
+     * @param sessionId the id of the session to retrieve from the EIS.
+     * @return the session identified by <tt>sessionId</tt> in the EIS.
+     * @throws UnknownSessionException if the id specified does not correspond to any session in the EIS.
+     */
+    public Session readSession(Serializable sessionId) throws UnknownSessionException {
+        Session s = doReadSession(sessionId);
+        if (s == null) {
+            throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
+        }
+        return s;
+    }
+
+    /**
+     * Subclass implementation hook that retrieves the Session object from the underlying EIS or {@code null} if a
+     * session with that ID could not be found.
+     *
+     * @param sessionId the id of the <tt>Session</tt> to retrieve.
+     * @return the Session in the EIS identified by <tt>sessionId</tt> or {@code null} if a
+     *         session with that ID could not be found.
+     */
+    protected abstract Session doReadSession(Serializable sessionId);
+
+}

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java?rev=927664&r1=927663&r2=927664&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java Fri Mar 26 01:41:04 2010
@@ -23,28 +23,33 @@ import org.apache.shiro.cache.CacheManag
 import org.apache.shiro.cache.CacheManagerAware;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.ValidatingSession;
 
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.Collections;
 
-
 /**
  * An CachingSessionDAO is a SessionDAO that provides a transparent caching layer between the components that
- * use it and the underlying EIS (Enterprise Information System) for enhanced performance.
+ * use it and the underlying EIS (Enterprise Information System) session backing store (for example, filesystem,
+ * database, enterprise grid/cloud, etc).
+ * <p/>
+ * This implementation caches all active sessions in a configured
+ * {@link #getActiveSessionsCache() activeSessionsCache}.  This property is {@code null} by default and if one is
+ * not explicitly set, a {@link #setCacheManager cacheManager} is expected to be configured which will in turn be used
+ * to acquire the {@code Cache} instance to use for the {@code activeSessionsCache}.
  * <p/>
- * <p>This implementation caches all active sessions in a cache created by a
- * {@link org.apache.shiro.cache.CacheManager}.  All <tt>SessionDAO</tt> methods are implemented by this class to employ
+ * All {@code SessionDAO} methods are implemented by this class to employ
  * caching behavior and delegates the actual EIS operations to respective do* methods to be implemented by
  * subclasses (doCreate, doRead, etc).
  *
  * @author Les Hazlewood
  * @since 0.2
  */
-public abstract class CachingSessionDAO implements SessionDAO, CacheManagerAware {
+public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {
 
     /**
-     * The default active sessions cache name, equal to <code>shiro-activeSessionCache</code>.
+     * The default active sessions cache name, equal to {@code shiro-activeSessionCache}.
      */
     public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
 
@@ -70,18 +75,23 @@ public abstract class CachingSessionDAO 
     }
 
     /**
-     * Sets the cacheManager to use for constructing the session cache.
+     * Sets the cacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
+     * one is not configured.
      *
      * @param cacheManager the manager to use for constructing the session cache.
      */
     public void setCacheManager(CacheManager cacheManager) {
         this.cacheManager = cacheManager;
-        //force cache reload:
-        this.activeSessions = null;
+        if (cacheManager != null) {
+            //force cache reload:
+            this.activeSessions = null;
+        }
     }
 
     /**
-     * Returns the CacheManager used by the implementation that creates the activeSessions Cache.
+     * Returns the CacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
+     * one is not configured.  That is, the {@code CacheManager} will only be used if the
+     * {@link #getActiveSessionsCache() activeSessionsCache} property is {@code null}.
      *
      * @return the CacheManager used by the implementation that creates the activeSessions Cache.
      */
@@ -90,7 +100,7 @@ public abstract class CachingSessionDAO 
     }
 
     /**
-     * Returns the name of the actives sessions cache to be returned by the <code>CacheManager</code>.  Unless
+     * Returns the name of the actives sessions cache to be returned by the {@code CacheManager}.  Unless
      * overridden by {@link #setActiveSessionsCacheName(String)}, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
      *
      * @return the name of the active sessions cache.
@@ -100,57 +110,65 @@ public abstract class CachingSessionDAO 
     }
 
     /**
-     * Sets the name of the active sessions cache to be returned by the <code>CacheManager</code>.  Defaults to
+     * Sets the name of the active sessions cache to be returned by the {@code CacheManager}.  Defaults to
      * {@link #ACTIVE_SESSION_CACHE_NAME}.
      *
-     * @param activeSessionsCacheName the name of the active sessions cache to be returned by the <code>CacheManager</code>.
+     * @param activeSessionsCacheName the name of the active sessions cache to be returned by the {@code CacheManager}.
      */
     public void setActiveSessionsCacheName(String activeSessionsCacheName) {
         this.activeSessionsCacheName = activeSessionsCacheName;
     }
 
     /**
-     * Returns the cache instance to use for storing active sessions.
+     * Returns the cache instance to use for storing active sessions.  If one is not available (it is {@code null}),
+     * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
+     * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
      *
-     * @return the cache instance to use for storing active sessions.
+     * @return the cache instance to use for storing active sessions or {@code null} if the {@code Cache} instance
+     *         should be retrieved from the
      */
     public Cache getActiveSessionsCache() {
         return this.activeSessions;
     }
 
     /**
+     * Sets the cache instance to use for storing active sessions.  If one is not set (it remains {@code null}),
+     * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
+     * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
+     *
+     * @param cache the cache instance to use for storing active sessions or {@code null} if the cache is to be
+     *              acquired from the {@link #setCacheManager configured} {@code CacheManager}.
+     */
+    public void setActiveSessionsCache(Cache cache) {
+        this.activeSessions = cache;
+    }
+
+    /**
      * Returns the active sessions cache, but if that cache instance is null, first lazily creates the cache instance
      * via the {@link #createActiveSessionsCache()} method and then returns the instance.
      * <p/>
-     * Note that this method will only return a non-null value code if the <code>CacheManager</code> has been set.  If
+     * Note that this method will only return a non-null value code if the {@code CacheManager} has been set.  If
      * not set, there will be no cache.
      *
      * @return the active sessions cache instance.
      */
     protected Cache getActiveSessionsCacheLazy() {
-        if (this.activeSessions == null) {
-            this.activeSessions = createActiveSessionsCache();
+        Cache activeSessions = getActiveSessionsCache();
+        if (activeSessions == null) {
+            activeSessions = createActiveSessionsCache();
+            setActiveSessionsCache(activeSessions);
         }
-        return this.activeSessions;
-    }
-
-    /**
-     * Sets the cache instance to use for storing active sessions.
-     *
-     * @param cache the cache instance to use for storing active sessions.
-     */
-    public void setActiveSessionsCache(Cache cache) {
-        this.activeSessions = cache;
+        return activeSessions;
     }
 
     /**
      * Creates a cache instance used to store active sessions.  Creation is done by first
-     * {@link #getCacheManager() acquiring} the <code>CacheManager</code>.  If the cache manager is not null, the
+     * {@link #getCacheManager() acquiring} the {@code CacheManager}.  If the cache manager is not null, the
      * cache returned is that resulting from the following call:
      * <pre>       String name = {@link #getActiveSessionsCacheName() getActiveSessionsCacheName()};
      * cacheManager.getCache(name);</pre>
      *
-     * @return a cache instance used to store active sessions, or <em>null</code> if the <code>CacheManager</code> has
+     * @return a cache instance used to store active sessions, or {@code null} if the {@code CacheManager} has
      *         not been set.
      */
     protected Cache createActiveSessionsCache() {
@@ -164,24 +182,23 @@ public abstract class CachingSessionDAO 
     }
 
     /**
-     * Creates the session by delegating EIS creation to subclasses via the {@link #doCreate} method, and then
-     * caches the session.
+     * Calls {@code super.create(session)}, then caches the session keyed by the returned {@code sessionId}, and then
+     * returns this {@code sessionId}.
      *
      * @param session Session object to create in the EIS and then cache.
      */
     public Serializable create(Session session) {
-        Serializable sessionId = doCreate(session);
-        verifySessionId(sessionId);
+        Serializable sessionId = super.create(session);
         cache(session, sessionId);
         return sessionId;
     }
 
     /**
-     * Returns the cached session with the corresponding <code>sessionId</code> or <code>null</code> if there is
+     * Returns the cached session with the corresponding {@code sessionId} or {@code null} if there is
      * no session cached under that id (or if there is no Cache).
      *
      * @param sessionId the id of the cached session to acquire.
-     * @return the cached session with the corresponding <code>sessionId</code>, or <code>null</code> if the session
+     * @return the cached session with the corresponding {@code sessionId}, or {@code null} if the session
      *         does not exist or is not cached.
      */
     protected Session getCachedSession(Serializable sessionId) {
@@ -197,11 +214,11 @@ public abstract class CachingSessionDAO 
 
     /**
      * Returns the Session with the specified id from the specified cache.  This method simply calls
-     * <code>cache.get(sessionId)</code> and can be overridden by subclasses for custom acquisition behavior.
+     * {@code cache.get(sessionId)} and can be overridden by subclasses for custom acquisition behavior.
      *
      * @param sessionId the id of the session to acquire.
      * @param cache     the cache to acquire the session from
-     * @return the cached session, or <code>null</code> if the session wasn't in the cache.
+     * @return the cached session, or {@code null} if the session wasn't in the cache.
      */
     protected Session getCachedSession(Serializable sessionId, Cache cache) {
         return (Session) cache.get(sessionId);
@@ -226,8 +243,8 @@ public abstract class CachingSessionDAO 
     }
 
     /**
-     * Caches the specified session in the given cache under the key of <code>sessionId</code>.  This implementation
-     * simply calls <code>cache.put(sessionId, session)</code> and can be overridden for custom behavior.
+     * Caches the specified session in the given cache under the key of {@code sessionId}.  This implementation
+     * simply calls {@code cache.put(sessionId,session)} and can be overridden for custom behavior.
      *
      * @param session   the session to cache
      * @param sessionId the id of the session, expected to be the cache key.
@@ -238,69 +255,26 @@ public abstract class CachingSessionDAO 
     }
 
     /**
-     * Ensures the sessionId returned from the subclass implementation of {@link #doCreate} is not null and not
-     * already in use.
-     *
-     * @param sessionId session id returned from the subclass implementation of {@link #doCreate}
-     */
-    protected void verifySessionId(Serializable sessionId) {
-        if (sessionId == null) {
-            String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
-            throw new IllegalStateException(msg);
-        }
-    }
-
-    /**
-     * Subclass hook to actually persist the given <tt>Session</tt> instance to the underlying EIS.
-     *
-     * @param session the Session instance to persist to the EIS.
-     * @return the id of the session created in the EIS (i.e. this is almost always a primary key and should be the
-     *         value returned from {@link org.apache.shiro.session.Session#getId() Session.getId()}.
-     */
-    protected abstract Serializable doCreate(Session session);
-
-    /**
-     * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt>.
-     * <p/>
-     * <p>Upon receiving the Session object from the subclass's {@link #doReadSession} implementation, it will be
-     * cached first and then returned to the caller.
+     * Attempts to acquire the Session from the cache first using the session ID as the cache key.  If no session
+     * is found, {@code super.readSession(sessionId)} is called to perform the actual retrieval.
      *
      * @param sessionId the id of the session to retrieve from the EIS.
-     * @return the session identified by <tt>sessionId</tt> in the EIS.
+     * @return the session identified by {@code sessionId} in the EIS.
      * @throws UnknownSessionException if the id specified does not correspond to any session in the cache or EIS.
      */
     public Session readSession(Serializable sessionId) throws UnknownSessionException {
-
         Session s = getCachedSession(sessionId);
-
-        if (s == null) {
-            s = doReadSession(sessionId);
-        }
-
         if (s == null) {
-            throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
+            s = super.readSession(sessionId);
         }
         return s;
     }
 
     /**
-     * Subclass implmentation hook to actually retrieve the Session object from the underlying EIS.
-     *
-     * @param sessionId the id of the <tt>Session</tt> to retrieve.
-     * @return the Session in the EIS identified by <tt>sessionId</tt>
-     */
-    protected abstract Session doReadSession(Serializable sessionId);
-
-    /**
-     * Updates the state of the given session to the EIS.
-     * <p/>
-     * <p>If the specified session was previously cached, and the session is now invalid,
-     * it will be removed from the cache.
-     * <p/>
-     * <p>If the specified session is not stopped or expired, and was not yet in the cache, it will be added to the
-     * cache.
-     * <p/>
-     * <p>Finally, this method calls {@link #doUpdate} for the subclass to actually push the object state to the EIS.
+     * Updates the state of the given session to the EIS by first delegating to
+     * {@link #doUpdate(org.apache.shiro.session.Session)}.  If the session is a {@link ValidatingSession}, it will
+     * be added to the cache only if it is {@link ValidatingSession#isValid()} and if invalid, will be removed from the
+     * cache.  If it is not a {@code ValidatingSession} instance, it will be added to the cache in any event.
      *
      * @param session the session object to update in the EIS.
      * @throws UnknownSessionException if no existing EIS session record exists with the
@@ -308,11 +282,19 @@ public abstract class CachingSessionDAO 
      */
     public void update(Session session) throws UnknownSessionException {
         doUpdate(session);
-        cache(session, session.getId());
+        if (session instanceof ValidatingSession) {
+            if (((ValidatingSession) session).isValid()) {
+                cache(session, session.getId());
+            } else {
+                uncache(session);
+            }
+        } else {
+            cache(session, session.getId());
+        }
     }
 
     /**
-     * Subclass implementation hook to actually persist the <tt>Session</tt>'s state to the underlying EIS.
+     * Subclass implementation hook to actually persist the {@code Session}'s state to the underlying EIS.
      *
      * @param session the session object whose state will be propagated to the EIS.
      */
@@ -370,7 +352,7 @@ public abstract class CachingSessionDAO 
         if (cache != null) {
             return cache.values();
         } else {
-            return Collections.EMPTY_LIST;
+            return Collections.emptySet();
         }
     }
 }

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java?rev=927664&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java Fri Mar 26 01:41:04 2010
@@ -0,0 +1,83 @@
+/*
+ * 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.eis;
+
+import org.apache.shiro.cache.AbstractCacheManager;
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.MapCache;
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * SessionDAO implementation that relies on an enterprise caching product as the EIS system of record for all sessions.
+ * It is expected that an injected {@link org.apache.shiro.cache.Cache Cache} or
+ * {@link org.apache.shiro.cache.CacheManager CacheManager} is backed by an enterprise caching product that can support
+ * all application sessions and/or provide disk paging for resilient data storage.
+ * <h2>Production Note</h2>
+ * This implementation defaults to using an in-memory map-based {@code CacheManager}, which is great for testing but
+ * will typically not scale for production environments and could easily cause {@code OutOfMemoryException}s.  Just
+ * don't forget to configure<b>*</b> an instance of this class with a production-grade {@code CacheManager} that can
+ * handle disk paging for large numbers of sessions and you'll be fine.
+ * <p/>
+ * <b>*</b>If you configure Shiro's {@code SecurityManager} instance with such a {@code CacheManager}, it will be
+ * automatically applied to an instance of this class and you won't need to explicitly set it in configuration.
+ * <h3>Implementation Details</h3>
+ * This implementation relies heavily on the {@link CachingSessionDAO parent class}'s transparent caching behavior for
+ * all storage operations with the enterprise caching product.  Because the parent class uses a {@code Cache} or
+ * {@code CacheManager} to perform caching, and the cache is considered the system of record, nothing further needs to
+ * be done for the {@link #doReadSession}, {@link #doUpdate} and {@link #doDelete} method implementations.  This class
+ * implements those methods as required by the parent class, but they essentially do nothing.
+ *
+ * @since 1.0
+ */
+public class EnterpriseCacheSessionDAO extends CachingSessionDAO {
+
+    public EnterpriseCacheSessionDAO() {
+        setCacheManager(new AbstractCacheManager() {
+            @Override
+            protected Cache createCache(String name) throws CacheException {
+                return new MapCache(name, new ConcurrentHashMap());
+            }
+        });
+    }
+
+    protected Serializable doCreate(Session session) {
+        Serializable sessionId = generateSessionId(session);
+        assignSessionId(session, sessionId);
+        return sessionId;
+    }
+
+    protected Session doReadSession(Serializable sessionId) {
+        return null; //should never execute because this implementation relies on parent class to access cache, which
+        //is where all sessions reside - it is the cache implementation that determines if the
+        //cache is memory only or disk-persistent, etc.
+    }
+
+    protected void doUpdate(Session session) {
+        //does nothing - parent class persists to cache.
+    }
+
+    protected void doDelete(Session session) {
+        //does nothing - parent class removes from cache.
+    }
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java?rev=927664&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java Fri Mar 26 01:41:04 2010
@@ -0,0 +1,43 @@
+/*
+ * 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.eis;
+
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * {@link SessionIdGenerator} that generates String values of JDK {@link java.util.UUID}'s as the session IDs.
+ *
+ * @since 1.0
+ */
+public class JavaUuidSessionIdGenerator implements SessionIdGenerator {
+
+    /**
+     * Ignores the method argument and simply returns
+     * {@code UUID}.{@link java.util.UUID#randomUUID() randomUUID()}.{@code toString()}.
+     *
+     * @param session the {@link Session} instance to which the ID will be applied.
+     * @return the String value of the JDK's next {@link UUID#randomUUID() randomUUID()}.
+     */
+    public Serializable generateId(Session session) {
+        return UUID.randomUUID().toString();
+    }
+}

Modified: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java?rev=927664&r1=927663&r2=927664&view=diff
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java (original)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java Fri Mar 26 01:41:04 2010
@@ -18,104 +18,90 @@
  */
 package org.apache.shiro.session.mgt.eis;
 
-import org.apache.shiro.cache.HashtableCacheManager;
 import org.apache.shiro.session.Session;
-import org.apache.shiro.session.mgt.SimpleSession;
-import org.apache.shiro.util.JavaEnvironment;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.util.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.Serializable;
-import java.util.Random;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 
 /**
- * Simple memory-based implementation of the SessionDAO that relies on its configured
- * {@link #setCacheManager CacheManager} for Session caching and in-memory persistence.
+ * Simple memory-based implementation of the SessionDAO that stores all of its sessions in an in-memory
+ * {@link ConcurrentMap}.  <b>This implementation does not page to disk and is therefore unsuitable for applications
+ * that could experience a large amount of sessions</b> and would therefore cause {@code OutOfMemoryException}s.  It is
+ * <em>not</em> recommended for production use in most environments.
+ * <h2>Memory Restrictions</h2>
+ * If your application is expected to host many sessions beyond what can be stored in the
+ * memory available to the JVM, it is highly recommended to use a different {@code SessionDAO} implementation which
+ * uses a more expansive or permanent backing data store.
  * <p/>
- * <p><b>PLEASE NOTE</b> the default CacheManager internal to this implementation is a
- * {@link org.apache.shiro.cache.HashtableCacheManager HashtableCacheManager}, which IS NOT RECOMMENDED for production environments.
- * <p/>
- * <p>If you
- * want to use the MemorySessionDAO in production environments, such as those that require session data to be
- * recoverable in case of a server restart, you should do one of two things (or both):
- * <p/>
- * <ul>
- * <li>Configure it with a production-quality CacheManager. The
- * {@code org.apache.shiro.cache.ehcache.EhCacheManager} is one such implementation.  It is not used by default
- * to prevent a forced runtime dependency on ehcache.jar that may not be required in many environments)</li><br/>
- * <li>If you need session information beyond their transient start/stop lifetimes, you should subclass this one and
- * override the <tt>do*</tt> methods to perform CRUD operations using an EIS-tier API (e.g. Hibernate/JPA/JCR/etc).
- * This class implementation does not retain sessions after they have been stopped or expired, so you would need to
- * override these methods to ensure Sessions can be accessed beyond Shiro's needs.</li>
- * </ul>
+ * In this case, it is recommended to instead use a custom
+ * {@link CachingSessionDAO} implementation that communicates with a higher-capacity data store of your choice
+ * (file system, database, etc).
+ * <h2>Changes in 1.0</h2>
+ * This implementation prior to 1.0 used to subclass the {@link CachingSessionDAO}, but this caused problems with many
+ * cache implementations that would expunge entries due to TTL settings, resulting in Sessions that would be randomly
+ * (and permanently) lost.  The Shiro 1.0 release refactored this implementation to be 100% memory-based (without
+ * {@code Cache} usage to avoid this problem.
  *
- * @author Les Hazlewood
+ * @see CachingSessionDAO
  * @since 0.1
  */
-public class MemorySessionDAO extends CachingSessionDAO {
-
-    //TODO - complete JavaDoc
+public class MemorySessionDAO extends AbstractSessionDAO {
 
     private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);
 
-    private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
-    private Random randomNumberGenerator = null;
+    private ConcurrentMap<Serializable, Session> sessions;
 
     public MemorySessionDAO() {
-        setCacheManager(new HashtableCacheManager());
-    }
-
-    private Random getRandomNumberGenerator() {
-        if (randomNumberGenerator == null) {
-            if (log.isInfoEnabled()) {
-                String msg = "On Java 1.4 platforms and below, there is no built-in UUID class (Java 1.5 and above " +
-                        "only) to use for Session ID generation - reverting to SecureRandom number generator.  " +
-                        "Although this is probably sufficient for all but high user volume applications, if you " +
-                        "see ID collision, you will want to upgrade to JDK 1.5 or better as soon as possible, or " +
-                        "subclass the " + getClass().getName() + " class and override the #generateNewSessionId() " +
-                        "method to use a better algorithm.";
-                log.info(msg);
-            }
-
-            try {
-                randomNumberGenerator = java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
-            } catch (java.security.NoSuchAlgorithmException e) {
-                randomNumberGenerator = new java.security.SecureRandom();
-            }
-        }
-        return randomNumberGenerator;
-    }
-
-    protected Serializable generateNewSessionId() {
-        if (JavaEnvironment.isAtLeastVersion15()) {
-            return java.util.UUID.randomUUID().toString();
-        } else {
-            return Long.toString(getRandomNumberGenerator().nextLong());
-        }
+        this.sessions = new ConcurrentHashMap<Serializable, Session>();
     }
 
     protected Serializable doCreate(Session session) {
-        Serializable sessionId = generateNewSessionId();
+        Serializable sessionId = generateSessionId(session);
         assignSessionId(session, sessionId);
+        storeSession(sessionId, session);
         return sessionId;
     }
 
-    protected void assignSessionId(Session session, Serializable sessionId) {
-        ((SimpleSession) session).setId(sessionId);
+    protected Session storeSession(Serializable id, Session session) {
+        if (id == null) {
+            throw new NullPointerException("id argument cannot be null.");
+        }
+        return sessions.putIfAbsent(id, session);
     }
 
     protected Session doReadSession(Serializable sessionId) {
-        return null; //should never execute because this implementation relies on parent class to access cache, which
-        //is where all sessions reside - it is the cache implementation that determines if the
-        //cache is memory only or disk-persistent, etc.
+        return sessions.get(sessionId);
     }
 
-    protected void doUpdate(Session session) {
-        //does nothing - parent class persists to cache.
+    public void update(Session session) throws UnknownSessionException {
+        storeSession(session.getId(), session);
     }
 
-    protected void doDelete(Session session) {
-        //does nothing - parent class removes from cache.
+    public void delete(Session session) {
+        if (session == null) {
+            throw new NullPointerException("session argument cannot be null.");
+        }
+        Serializable id = session.getId();
+        if (id != null) {
+            sessions.remove(id);
+        }
     }
+
+    public Collection<Session> getActiveSessions() {
+        Collection<Session> values = sessions.values();
+        if (CollectionUtils.isEmpty(values)) {
+            return Collections.emptySet();
+        } else {
+            return Collections.unmodifiableCollection(values);
+        }
+    }
+
 }

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java?rev=927664&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java Fri Mar 26 01:41:04 2010
@@ -0,0 +1,69 @@
+/*
+ * 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.eis;
+
+import org.apache.shiro.session.Session;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Random;
+
+/**
+ * Generates session IDs by using a {@link Random} instance to generate random IDs. The default {@code Random}
+ * implementation is a {@link java.security.SecureRandom SecureRandom} with the {@code SHA1PRNG} algorithm.
+ *
+ * @since 1.0
+ */
+public class RandomSessionIdGenerator implements SessionIdGenerator {
+
+    private static final Logger log = LoggerFactory.getLogger(RandomSessionIdGenerator.class);
+
+    private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
+    private Random random;
+
+    public RandomSessionIdGenerator() {
+        try {
+            this.random = java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
+        } catch (java.security.NoSuchAlgorithmException e) {
+            log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform.  Using the " +
+                    "platform's default SecureRandom algorithm.", e);
+            this.random = new java.security.SecureRandom();
+        }
+    }
+
+    public Random getRandom() {
+        return this.random;
+    }
+
+    public void setRandom(Random random) {
+        this.random = random;
+    }
+
+    /**
+     * Returns the String value of the configured {@link Random}'s {@link Random#nextLong() nextLong()} invocation.
+     *
+     * @param session the {@link Session} instance to which the ID will be applied.
+     * @return the String value of the configured {@link Random}'s {@link Random#nextLong()} invocation.
+     */
+    public Serializable generateId(Session session) {
+        //ignore the argument - just call the Random:
+        return Long.toString(getRandom().nextLong());
+    }
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java?rev=927664&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java Fri Mar 26 01:41:04 2010
@@ -0,0 +1,51 @@
+/*
+ * 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.eis;
+
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+
+/**
+ * Interface allowing pluggable session ID generation strategies to be used with various {@link SessionDAO}
+ * implementations.
+ * <h2>Usage</h2>
+ * SessionIdGenerators are usually only used when ID generation is separate from creating the
+ * Session record in the EIS data store.  Some EIS data stores, such as relational databases, can generate the id
+ * at the same time the record is created, such as when using auto-generated primary keys.  In these cases, a
+ * SessionIdGenerator does not need to be configured.
+ * <p/>
+ * However, if you want to customize how session IDs are created before persisting the Session record into the data
+ * store, you can implement this interface and typically inject it into an {@link AbstractSessionDAO} instance.
+ *
+ * @see org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator JavaUuidSessionIdGenerator
+ * @see org.apache.shiro.session.mgt.eis.RandomSessionIdGenerator RandomSessionIdGenerator
+ * @since 1.0
+ */
+public interface SessionIdGenerator {
+
+    /**
+     * Generates a new ID to be applied to the specified {@code Session} instance.
+     *
+     * @param session the {@link Session} instance to which the ID will be applied.
+     * @return the id to assign to the specified {@link Session} instance before adding a record to the EIS data store.
+     */
+    Serializable generateId(Session session);
+
+}

Modified: incubator/shiro/trunk/samples/spring-client/pom.xml
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/samples/spring-client/pom.xml?rev=927664&r1=927663&r2=927664&view=diff
==============================================================================
--- incubator/shiro/trunk/samples/spring-client/pom.xml (original)
+++ incubator/shiro/trunk/samples/spring-client/pom.xml Fri Mar 26 01:41:04 2010
@@ -1,153 +1,154 @@
-<?xml	version="1.0"	encoding="UTF-8"?>
+<?xml    version="1.0"    encoding="UTF-8"?>
 <!--
-	~	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.
-	-->
+  ~ 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.
+  -->
 <!--suppress osmorcNonOsgiMavenDependency	-->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
-	<parent>
-		<groupId>org.apache.shiro.samples</groupId>
-		<artifactId>shiro-samples</artifactId>
-		<version>1.0-incubating-SNAPSHOT</version>
-	</parent>
-
-	<modelVersion>4.0.0</modelVersion>
-	<artifactId>samples-spring-client</artifactId>
-	<name>Apache Shiro :: Samples :: Spring Client</name>
-	<description>A webstart application used to demonstrate Apache Shiro session and security management.</description>
-	<packaging>jar</packaging>
-	
-	<properties>
-		<shiro.session.id>${sessionId}</shiro.session.id>
-	</properties>
-	
-	<dependencies>
-		<dependency>
-			<groupId>log4j</groupId>
-			<artifactId>log4j</artifactId>
-			<scope>compile</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.shiro</groupId>
-			<artifactId>shiro-core</artifactId>
-			<exclusions>
-				<exclusion>
-					<groupId>commons-beanutils</groupId>
-					<artifactId>commons-beanutils</artifactId>
-				</exclusion>
-			</exclusions>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.shiro</groupId>
-			<artifactId>shiro-spring</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.shiro</groupId>
-			<artifactId>shiro-web</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework</groupId>
-			<artifactId>spring-aop</artifactId>
-			<version>2.5.6</version>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework</groupId>
-			<artifactId>spring-context-support</artifactId>
-			<version>2.5.6</version>
-			<exclusions>
-				<exclusion>
-					<groupId>commons-logging</groupId>
-					<artifactId>commons-logging</artifactId>
-				</exclusion>
-			</exclusions>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework</groupId>
-			<artifactId>spring-web</artifactId>
-			<version>2.5.6</version>
-			<exclusions>
-				<exclusion>
-					<groupId>commons-logging</groupId>
-					<artifactId>commons-logging</artifactId>
-				</exclusion>
-			</exclusions>
-		</dependency>
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>slf4j-api</artifactId>
-			<version>1.5.6</version>
-			<scope>compile</scope>
-		</dependency>			
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>slf4j-log4j12</artifactId>
-			<version>1.5.6</version>
-			<scope>compile</scope>
-		</dependency>			
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>jcl-over-slf4j</artifactId>
-			<version>1.5.6</version>
-			<scope>compile</scope>
-		</dependency>			
-	</dependencies>
-	
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.codehaus.mojo.webstart</groupId>
-				<artifactId>webstart-maven-plugin</artifactId>
-				<version>1.0-alpha-2</version>
-				<executions>
-					<execution>
-						<phase>package</phase>
-						<goals>
-							<goal>jnlp-inline</goal>
-						</goals>
-					</execution>
-				</executions>
-				<configuration>
-					<!--	JNLP generation	-->
-					<jnlp>
-						<!--	default	values -->
-						<!--inputTemplateResourcePath>${project.basedir}</inputTemplateResourcePath-->
-						<!--inputTemplate>src/main/jnlp/template.vm</inputTemplate--> <!--	relative to	inputTemplateResourcePath	-->
-						<outputFile>shiro.jnlp.jsp</outputFile> <!--	defaults to	launch.jnlp	-->
-	
-						<mainClass>org.apache.shiro.samples.spring.ui.WebStartDriver</mainClass>
-					</jnlp>
-	
-	
-					<sign>
-						<keystore>jsecurity-sample.jks</keystore>
-						<storepass>jsecurity</storepass>	
-						<alias>jsecurity</alias>
-						<verify>false</verify>
-					</sign>
-	
-					<!--	BUILDING PROCESS -->
-					<pack200>true</pack200>
-					<verbose>false</verbose>
-	
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.0-incubating-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-spring-client</artifactId>
+    <name>Apache Shiro :: Samples :: Spring Client</name>
+    <description>A webstart application used to demonstrate Apache Shiro session and security management.</description>
+    <packaging>jar</packaging>
+
+    <properties>
+        <shiro.session.id>${sessionId}</shiro.session.id>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-beanutils</groupId>
+                    <artifactId>commons-beanutils</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aop</artifactId>
+            <version>2.5.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+            <version>2.5.6</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>2.5.6</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.5.6</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.5.6</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <version>1.5.6</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo.webstart</groupId>
+                <artifactId>webstart-maven-plugin</artifactId>
+                <version>1.0-alpha-2</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jnlp-inline</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <!--	JNLP generation	-->
+                    <jnlp>
+                        <!--	default	values -->
+                        <!--inputTemplateResourcePath>${project.basedir}</inputTemplateResourcePath-->
+                        <!--inputTemplate>src/main/jnlp/template.vm</inputTemplate--> <!--	relative to	inputTemplateResourcePath	-->
+                        <outputFile>shiro.jnlp.jsp</outputFile>
+                        <!--	defaults to	launch.jnlp	-->
+
+                        <mainClass>org.apache.shiro.samples.spring.ui.WebStartDriver</mainClass>
+                    </jnlp>
+
+
+                    <sign>
+                        <keystore>jsecurity-sample.jks</keystore>
+                        <storepass>jsecurity</storepass>
+                        <alias>jsecurity</alias>
+                        <verify>false</verify>
+                    </sign>
+
+                    <!--	BUILDING PROCESS -->
+                    <pack200>true</pack200>
+                    <verbose>false</verbose>
+
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 
 </project>

Modified: incubator/shiro/trunk/samples/spring/pom.xml
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/samples/spring/pom.xml?rev=927664&r1=927663&r2=927664&view=diff
==============================================================================
--- incubator/shiro/trunk/samples/spring/pom.xml (original)
+++ incubator/shiro/trunk/samples/spring/pom.xml Fri Mar 26 01:41:04 2010
@@ -1,172 +1,170 @@
-<?xml	version="1.0"	encoding="UTF-8"?>
+<?xml    version="1.0"    encoding="UTF-8"?>
 <!--
-	~	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.
-	-->
+  ~ 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.
+  -->
 <!--suppress osmorcNonOsgiMavenDependency	-->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
-	<parent>
-		<groupId>org.apache.shiro.samples</groupId>
-		<artifactId>shiro-samples</artifactId>
-		<version>1.0-incubating-SNAPSHOT</version>
-	</parent>
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.0-incubating-SNAPSHOT</version>
+    </parent>
 
-	<modelVersion>4.0.0</modelVersion>
-	<artifactId>samples-spring</artifactId>
-	<name>Apache Shiro ::	Samples	:: Spring</name>
-	<packaging>war</packaging>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-spring</artifactId>
+    <name>Apache Shiro :: Samples :: Spring</name>
+    <packaging>war</packaging>
 
-	<build>
-		<plugins>
-			<plugin>
-				<!-- Note	that you need	to run mvn jetty:run-exploded	to test	the	webstart application -->
-				<groupId>org.mortbay.jetty</groupId>
-				<artifactId>maven-jetty-plugin</artifactId>
-				<version>${jetty.version}</version>
-				<configuration>
-					<contextPath>/shiro</contextPath>
-					<connectors>
-						<connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
-							<port>9080</port>
-							<maxIdleTime>60000</maxIdleTime>
-						</connector>
-					</connectors>
-					<requestLog	implementation="org.mortbay.jetty.NCSARequestLog">
-						<filename>./target/yyyy_mm_dd.request.log</filename>
-						<retainDays>90</retainDays>
-						<append>true</append>
-						<extended>false</extended>
-						<logTimeZone>GMT</logTimeZone>
-					</requestLog>
-				</configuration>
-				<dependencies>
-					<dependency>
-						<groupId>hsqldb</groupId>
-						<artifactId>hsqldb</artifactId>
-						<version>${hsqldb.version}</version>
-					</dependency>
-				</dependencies>
-			</plugin>
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>dependency-maven-plugin</artifactId>
-				<executions>
-					<execution>
-						<phase>generate-resources</phase>
-						<goals>
-							<goal>unpack</goal>
-						</goals>
-					</execution>
-				</executions>
-				<configuration>
-					<artifactItems>
-						<artifactItem>
-							<groupId>org.apache.shiro.samples</groupId>
-							<artifactId>samples-spring-client</artifactId>
-							<version>${project.version}</version>
-							<type>zip</type>
-						</artifactItem>
-					</artifactItems>
-					<outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
-				</configuration>
-			</plugin>
-			<plugin>
-				<artifactId>maven-antrun-plugin</artifactId>
-				<executions>
-					<execution>
-						<id>replace-jnlp-file</id>
-						<phase>process-resources</phase>
-						<configuration>
-							<tasks>
-								<!--	move would be	more appropriate but it	would	fail on	repetitive executions	of jetty:run for example,	
-								leaving the original	in place doesn't hurt	-->
-								<copy 
-									file="${project.build.directory}/${project.build.finalName}/shiro.jnlp.jsp"
-									todir="${project.build.directory}/${project.build.finalName}/WEB-INF/resources"
-								/>
-							</tasks>
-						</configuration>
-						<goals>
-							<goal>run</goal>
-						</goals>
-					</execution>
-				</executions>
-			</plugin>
-		</plugins>
-	</build>
+    <build>
+        <plugins>
+            <plugin>
+                <!-- Note	that you need	to run mvn jetty:run-exploded	to test	the	webstart application -->
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>maven-jetty-plugin</artifactId>
+                <version>${jetty.version}</version>
+                <configuration>
+                    <contextPath>/shiro</contextPath>
+                    <connectors>
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+                            <port>9080</port>
+                            <maxIdleTime>60000</maxIdleTime>
+                        </connector>
+                    </connectors>
+                    <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
+                        <filename>./target/yyyy_mm_dd.request.log</filename>
+                        <retainDays>90</retainDays>
+                        <append>true</append>
+                        <extended>false</extended>
+                        <logTimeZone>GMT</logTimeZone>
+                    </requestLog>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>hsqldb</groupId>
+                        <artifactId>hsqldb</artifactId>
+                        <version>${hsqldb.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>dependency-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <artifactItems>
+                        <artifactItem>
+                            <groupId>org.apache.shiro.samples</groupId>
+                            <artifactId>samples-spring-client</artifactId>
+                            <version>${project.version}</version>
+                            <type>zip</type>
+                        </artifactItem>
+                    </artifactItems>
+                    <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>replace-jnlp-file</id>
+                        <phase>process-resources</phase>
+                        <configuration>
+                            <tasks>
+                                <!-- move would be more appropriate but it would fail on repetitive executions of
+                                     jetty:run for example, leaving the original in place doesn't hurt -->
+                                <copy file="${project.build.directory}/${project.build.finalName}/shiro.jnlp.jsp"
+                                      todir="${project.build.directory}/${project.build.finalName}/WEB-INF/resources"/>
+                            </tasks>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 
-	<dependencies>
-		<dependency>
-			<groupId>org.apache.shiro.samples</groupId>
-			<artifactId>samples-spring-client</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.shiro</groupId>
-			<artifactId>shiro-core</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.shiro</groupId>
-			<artifactId>shiro-spring</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.shiro</groupId>
-			<artifactId>shiro-web</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>servlet-api</artifactId>
-			<scope>provided</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>slf4j-log4j12</artifactId>
-			<scope>runtime</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>jcl-over-slf4j</artifactId>
-			<scope>runtime</scope>
-		</dependency>
-		<dependency>
-			<groupId>log4j</groupId>
-			<artifactId>log4j</artifactId>
-			<scope>runtime</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework</groupId>
-			<artifactId>spring</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework</groupId>
-			<artifactId>spring-webmvc</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>hsqldb</groupId>
-			<artifactId>hsqldb</artifactId>
-			<version>${hsqldb.version}</version>
-			<scope>runtime</scope>
-		</dependency>
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>jstl</artifactId>
-			<version>1.2</version>
-			<scope>runtime</scope>
-		</dependency>
-	</dependencies>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro.samples</groupId>
+            <artifactId>samples-spring-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>hsqldb</groupId>
+            <artifactId>hsqldb</artifactId>
+            <version>${hsqldb.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>jstl</artifactId>
+            <version>1.2</version>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
 </project>