Merged changes from the import branch that were accidentally committed into the trunk.

git-svn-id: https://svn.apache.org/repos/asf/incubator/jsecurity/trunk@749350 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/common.ant.xml b/common.ant.xml
index 4d833a0..74fb5b8 100644
--- a/common.ant.xml
+++ b/common.ant.xml
@@ -95,9 +95,10 @@
                 <fileset dir="@{srcdir}">
                     <include name="**/*"/>
                     <exclude name="**/*.java"/>
+                    <exclude name="**/*.tld"/>
                 </fileset>
             </copy>
-            <copy todir="@{destdir}" preservelastmodified="true">
+            <copy todir="@{destdir}/META-INF" flatten="true" preservelastmodified="true">
                 <fileset dir="@{srcdir}">
                     <include name="**/*.tld"/>
                 </fileset>
@@ -120,6 +121,7 @@
         <jar jarfile="${dist.jar}">
             <fileset dir="${classes.dir}">
                 <include name="**"/>
+                <exclude name="META-INF/**"/>
             </fileset>
             <metainf dir="${classes.dir}/META-INF">
                 <include name="**"/>
diff --git a/core/src/org/jsecurity/authc/pam/AbstractAuthenticationStrategy.java b/core/src/org/jsecurity/authc/pam/AbstractAuthenticationStrategy.java
index 9161af3..adf2baa 100644
--- a/core/src/org/jsecurity/authc/pam/AbstractAuthenticationStrategy.java
+++ b/core/src/org/jsecurity/authc/pam/AbstractAuthenticationStrategy.java
@@ -24,14 +24,14 @@
 import java.util.Collection;

 

 /**

- * Abstract base implementation for JSecurity's concrete <code>ModularAuthenticationStrategy</code>

+ * Abstract base implementation for JSecurity's concrete <code>AuthenticationStrategy</code>

  * implementations.

  *

  * @author Jeremy Haile

  * @author Les Hazlewood

  * @since 0.9

  */

-public abstract class AbstractAuthenticationStrategy implements ModularAuthenticationStrategy {

+public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy {

 

     /**

      * Simply returns <code>new {@link SimpleAuthenticationInfo SimpleAuthenticationInfo}();</code>, which supports

diff --git a/core/src/org/jsecurity/authc/pam/AllSuccessfulModularAuthenticationStrategy.java b/core/src/org/jsecurity/authc/pam/AllSuccessfulStrategy.java
similarity index 95%
rename from core/src/org/jsecurity/authc/pam/AllSuccessfulModularAuthenticationStrategy.java
rename to core/src/org/jsecurity/authc/pam/AllSuccessfulStrategy.java
index bfb233f..29f1407 100644
--- a/core/src/org/jsecurity/authc/pam/AllSuccessfulModularAuthenticationStrategy.java
+++ b/core/src/org/jsecurity/authc/pam/AllSuccessfulStrategy.java
@@ -27,7 +27,7 @@
 import org.jsecurity.realm.Realm;
 
 /**
- * <tt>ModularAuthenticationStrategy</tt> implementation that requires <em>all</em> configured realms to
+ * <tt>AuthenticationStrategy</tt> implementation that requires <em>all</em> configured realms to
  * <b>successfully</b> process the submitted <tt>AuthenticationToken</tt> during the log-in attempt.
  *
  * <p>If one or more realms do not support the submitted token, or one or more are unable to acquire
@@ -37,10 +37,10 @@
  * @author Les Hazlewood
  * @since 0.2
  */
-public class AllSuccessfulModularAuthenticationStrategy extends AbstractAuthenticationStrategy {
+public class AllSuccessfulStrategy extends AbstractAuthenticationStrategy {
 
     /** Private class log instance. */
-    private static final Log log = LogFactory.getLog(AllSuccessfulModularAuthenticationStrategy.class);
+    private static final Log log = LogFactory.getLog(AllSuccessfulStrategy.class);
 
     /**
      * Because all realms in this strategy must complete successfully, this implementation ensures that the given
diff --git a/core/src/org/jsecurity/authc/pam/AtLeastOneSuccessfulModularAuthenticationStrategy.java b/core/src/org/jsecurity/authc/pam/AtLeastOneSuccessfulStrategy.java
similarity index 87%
rename from core/src/org/jsecurity/authc/pam/AtLeastOneSuccessfulModularAuthenticationStrategy.java
rename to core/src/org/jsecurity/authc/pam/AtLeastOneSuccessfulStrategy.java
index 0206507..7a23069 100644
--- a/core/src/org/jsecurity/authc/pam/AtLeastOneSuccessfulModularAuthenticationStrategy.java
+++ b/core/src/org/jsecurity/authc/pam/AtLeastOneSuccessfulStrategy.java
@@ -23,7 +23,7 @@
 import org.jsecurity.authc.AuthenticationToken;
 
 /**
- * <tt>ModularAuthenticationStrategy</tt> implementation that requires <em>at least one</em> configured realm to
+ * <tt>AuthenticationStrategy</tt> implementation that requires <em>at least one</em> configured realm to
  * successfully process the submitted <tt>AuthenticationToken</tt> during the log-in attempt.
  *
  * <p>This means any number of configured realms do not have to support the submitted log-in token, or they may
@@ -33,13 +33,13 @@
  * <p>Note that this implementation will aggregate the account data from <em>all</em> successfully consulted
  * realms during the authentication attempt. If you want only the account data from the first successfully
  * consulted realm and want to ignore all subsequent realms, use the
- * {@link FirstSuccessfulAuthenticationStrategy FirstSuccessfulAuthenticationStrategy} instead.
+ * {@link FirstSuccessfulStrategy FirstSuccessfulAuthenticationStrategy} instead.
  *
  * @author Les Hazlewood
- * @see FirstSuccessfulAuthenticationStrategy FirstSuccessfulAuthenticationStrategy
+ * @see FirstSuccessfulStrategy FirstSuccessfulAuthenticationStrategy
  * @since 0.2
  */
-public class AtLeastOneSuccessfulModularAuthenticationStrategy extends AbstractAuthenticationStrategy {
+public class AtLeastOneSuccessfulStrategy extends AbstractAuthenticationStrategy {
 
     /**
      * Ensures that the <code>aggregate</code> method argument is not <code>null</code> and
diff --git a/core/src/org/jsecurity/authc/pam/ModularAuthenticationStrategy.java b/core/src/org/jsecurity/authc/pam/AuthenticationStrategy.java
similarity index 93%
rename from core/src/org/jsecurity/authc/pam/ModularAuthenticationStrategy.java
rename to core/src/org/jsecurity/authc/pam/AuthenticationStrategy.java
index 4676b06..707f8e6 100644
--- a/core/src/org/jsecurity/authc/pam/ModularAuthenticationStrategy.java
+++ b/core/src/org/jsecurity/authc/pam/AuthenticationStrategy.java
@@ -26,7 +26,7 @@
 import java.util.Collection;
 
 /**
- * A <tt>ModularAuthenticationStrategy</tt> implementation assists the {@link ModularRealmAuthenticator} during the
+ * A <tt>AuthenticationStrategy</tt> implementation assists the {@link ModularRealmAuthenticator} during the
  * log-in process in a pluggable realm (PAM) environment.
  *
  * <p>The <tt>ModularRealmAuthenticator</tt> will consult implementations of this interface on what to do during each
@@ -34,12 +34,12 @@
  * attempt must be successful for all realms, only 1 or more realms, no realms, etc.
  *
  * @author Les Hazlewood
- * @see org.jsecurity.authc.pam.AllSuccessfulModularAuthenticationStrategy AllSuccessfulModularAuthenticationStrategy
- * @see org.jsecurity.authc.pam.AtLeastOneSuccessfulModularAuthenticationStrategy AtLeastOneSuccessfulModularAuthenticationStrategy
- * @see org.jsecurity.authc.pam.FirstSuccessfulAuthenticationStrategy FirstSuccessfulAuthenticationStrategy
+ * @see AllSuccessfulStrategy AllSuccessfulAuthenticationStrategy
+ * @see AtLeastOneSuccessfulStrategy AtLeastOneSuccessfulAuthenticationStrategy
+ * @see FirstSuccessfulStrategy FirstSuccessfulAuthenticationStrategy
  * @since 0.2
  */
-public interface ModularAuthenticationStrategy {
+public interface AuthenticationStrategy {
 
     /**
      * Method invoked by the ModularAuthenticator signifying that the authentication process is about to begin for the
diff --git a/core/src/org/jsecurity/authc/pam/FirstSuccessfulAuthenticationStrategy.java b/core/src/org/jsecurity/authc/pam/FirstSuccessfulStrategy.java
similarity index 84%
rename from core/src/org/jsecurity/authc/pam/FirstSuccessfulAuthenticationStrategy.java
rename to core/src/org/jsecurity/authc/pam/FirstSuccessfulStrategy.java
index e7af122..c8637a5 100644
--- a/core/src/org/jsecurity/authc/pam/FirstSuccessfulAuthenticationStrategy.java
+++ b/core/src/org/jsecurity/authc/pam/FirstSuccessfulStrategy.java
@@ -26,17 +26,17 @@
 import java.util.Collection;

 

 /**

- * {@link ModularAuthenticationStrategy} implementation that only accepts the account data from

+ * {@link AuthenticationStrategy} implementation that only accepts the account data from

  * the first successfully consulted Realm and ignores all subsequent realms.  This is slightly

  * different behavior than

- * {@link org.jsecurity.authc.pam.AtLeastOneSuccessfulModularAuthenticationStrategy AtLeastOneSuccessfulModularAuthenticationStrategy},

+ * {@link AtLeastOneSuccessfulStrategy AtLeastOneSuccessfulAuthenticationStrategy},

  * so please review both to see which one meets your needs better.

  *

  * @author Les Hazlewood

- * @see org.jsecurity.authc.pam.AtLeastOneSuccessfulModularAuthenticationStrategy AtLeastOneSuccessfulModularAuthenticationStrategy

+ * @see AtLeastOneSuccessfulStrategy AtLeastOneSuccessfulAuthenticationStrategy

  * @since 0.9

  */

-public class FirstSuccessfulAuthenticationStrategy extends AbstractAuthenticationStrategy {

+public class FirstSuccessfulStrategy extends AbstractAuthenticationStrategy {

 

     /**

      * Returns <code>null</code> immediately, relying on this class's {@link #merge merge} implementation to return

diff --git a/core/src/org/jsecurity/authc/pam/ModularRealmAuthenticator.java b/core/src/org/jsecurity/authc/pam/ModularRealmAuthenticator.java
index 8797f33..3bda973 100644
--- a/core/src/org/jsecurity/authc/pam/ModularRealmAuthenticator.java
+++ b/core/src/org/jsecurity/authc/pam/ModularRealmAuthenticator.java
@@ -46,7 +46,7 @@
  * authenticator allows customized behavior for interpreting what happens when interacting with multiple realms - for
  * example, you might require all realms to be successful during the attempt, or perhaps only at least one must be
  * successful, or some other interpretation.  This customized behavior can be performed via the use of a
- * {@link #setModularAuthenticationStrategy(ModularAuthenticationStrategy) ModularAuthenticationStrategy}, which
+ * {@link #setAuthenticationStrategy(AuthenticationStrategy) AuthenticationStrategy}, which
  * you can inject as a property of this class.
  *
  * <p>The strategy object provides callback methods that allow you to
@@ -54,13 +54,13 @@
  * in a mult-realm scenario, the strategy object is only utilized when more than one Realm is configured.
  *
  * <p>For greater security in a multi-realm configuration, unless overridden, the default implementation is the
- * {@link AllSuccessfulModularAuthenticationStrategy AllSuccessfulModularAuthenticationStrategy}
+ * {@link AllSuccessfulStrategy AllSuccessfulAuthenticationStrategy}
  *
  * @author Jeremy Haile
  * @author Les Hazlewood
  * @see #setRealms
- * @see AllSuccessfulModularAuthenticationStrategy
- * @see AtLeastOneSuccessfulModularAuthenticationStrategy
+ * @see AllSuccessfulStrategy
+ * @see AtLeastOneSuccessfulStrategy
  * @since 0.1
  */
 public class ModularRealmAuthenticator extends AbstractAuthenticator {
@@ -81,27 +81,27 @@
     /**
      * The authentication strategy to use during authentication attempts.
      */
-    private ModularAuthenticationStrategy modularAuthenticationStrategy;
+    private AuthenticationStrategy authenticationStrategy;
 
     /*--------------------------------------------
     |         C O N S T R U C T O R S           |
     ============================================*/
     /**
      * Default no-argument constructor which
-     * {@link #setModularAuthenticationStrategy(ModularAuthenticationStrategy) enables}  a
-     * {@link org.jsecurity.authc.pam.AllSuccessfulModularAuthenticationStrategy AllSuccessfulModularAuthenticationStrategy}
+     * {@link #setAuthenticationStrategy(AuthenticationStrategy) enables}  a
+     * {@link AllSuccessfulStrategy AllSuccessfulAuthenticationStrategy}
      * by default.
      */
     public ModularRealmAuthenticator() {
-        ModularAuthenticationStrategy strategy = new AllSuccessfulModularAuthenticationStrategy();
-        setModularAuthenticationStrategy(strategy);
+        AuthenticationStrategy strategy = new AllSuccessfulStrategy();
+        setAuthenticationStrategy(strategy);
     }
 
     /**
      * Constructor which initializes this <code>Authenticator</code> with a single realm to use during
      * an authentiation attempt.  Because
-     * this would set a single realm, no {@link #setModularAuthenticationStrategy(ModularAuthenticationStrategy)
-     * modularAuthenticationStrategy} would be used during authentication attempts.
+     * this would set a single realm, no {@link #setAuthenticationStrategy(AuthenticationStrategy)
+     * AuthenticationStrategy} would be used during authentication attempts.
      *
      * @param realm the realm to consult during an authentication attempt.
      */
@@ -113,7 +113,7 @@
      * Constructor which initializes this <code>Authenticator</code> with multiple realms that will be
      * consulted during an authentication attempt, effectively enabling PAM (Pluggable Authentication Module)
      * behavior according to the configured
-     * {@link #setModularAuthenticationStrategy(ModularAuthenticationStrategy) ModularAuthenticationStrategy}.
+     * {@link #setAuthenticationStrategy(AuthenticationStrategy) AuthenticationStrategy}.
      *
      * @param realms the realms to consult during an authentication attempt.
      */
@@ -155,29 +155,29 @@
     }
 
     /**
-     * Returns the <tt>ModularAuthenticationStrategy</tt> utilized by this modular authenticator during a multi-realm
+     * Returns the <tt>AuthenticationStrategy</tt> utilized by this modular authenticator during a multi-realm
      * log-in attempt.  This object is only used when two or more Realms are configured.
      *
      * <p>Unless overridden by
-     * the {@link #setModularAuthenticationStrategy(ModularAuthenticationStrategy)} method, the default implementation
-     * is the {@link AllSuccessfulModularAuthenticationStrategy}.
+     * the {@link #setAuthenticationStrategy(AuthenticationStrategy)} method, the default implementation
+     * is the {@link AllSuccessfulStrategy}.
      *
-     * @return the <tt>ModularAuthenticationStrategy</tt> utilized by this modular authenticator during a log-in attempt.
+     * @return the <tt>AuthenticationStrategy</tt> utilized by this modular authenticator during a log-in attempt.
      * @since 0.2
      */
-    public ModularAuthenticationStrategy getModularAuthenticationStrategy() {
-        return modularAuthenticationStrategy;
+    public AuthenticationStrategy getAuthenticationStrategy() {
+        return authenticationStrategy;
     }
 
     /**
-     * Allows overriding the default <tt>ModularAuthenticationStrategy</tt> utilized during multi-realm log-in attempts.
+     * Allows overriding the default <tt>AuthenticationStrategy</tt> utilized during multi-realm log-in attempts.
      * This object is only used when two or more Realms are configured.
      *
-     * @param modularAuthenticationStrategy the strategy implementation to use during log-in attempts.
+     * @param authenticationStrategy the strategy implementation to use during log-in attempts.
      * @since 0.2
      */
-    public void setModularAuthenticationStrategy(ModularAuthenticationStrategy modularAuthenticationStrategy) {
-        this.modularAuthenticationStrategy = modularAuthenticationStrategy;
+    public void setAuthenticationStrategy(AuthenticationStrategy authenticationStrategy) {
+        this.authenticationStrategy = authenticationStrategy;
     }
 
     /*--------------------------------------------
@@ -223,7 +223,7 @@
     }
 
     /**
-     * Performs the multi-realm authentication attempt by calling back to a {@link ModularAuthenticationStrategy} object
+     * Performs the multi-realm authentication attempt by calling back to a {@link AuthenticationStrategy} object
      * as each realm is consulted for <tt>AuthenticationInfo</tt> for the specified <tt>token</tt>.
      *
      * @param realms the multiple realms configured on this Authenticator instance.
@@ -233,7 +233,7 @@
      */
     protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
 
-        ModularAuthenticationStrategy strategy = getModularAuthenticationStrategy();
+        AuthenticationStrategy strategy = getAuthenticationStrategy();
 
         AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
 
diff --git a/core/src/org/jsecurity/authc/pam/UnsupportedTokenException.java b/core/src/org/jsecurity/authc/pam/UnsupportedTokenException.java
index b96faf6..070e756 100644
--- a/core/src/org/jsecurity/authc/pam/UnsupportedTokenException.java
+++ b/core/src/org/jsecurity/authc/pam/UnsupportedTokenException.java
@@ -26,7 +26,7 @@
  * supported by one or more configured {@link org.jsecurity.realm.Realm Realm}s.
  *
  * @author Les Hazlewood
- * @see ModularAuthenticationStrategy
+ * @see AuthenticationStrategy
  * @since 0.2
  */
 public class UnsupportedTokenException extends AuthenticationException {
diff --git a/core/src/org/jsecurity/authc/pam/package-info.java b/core/src/org/jsecurity/authc/pam/package-info.java
index 4763ab6..4a4a7d5 100644
--- a/core/src/org/jsecurity/authc/pam/package-info.java
+++ b/core/src/org/jsecurity/authc/pam/package-info.java
@@ -27,6 +27,6 @@
  * <p/>

  * How the <code>ModularRealmAuthenticator</code> actually coordinates this behavior is configurable based on your

  * application's needs using an injectible

- * {@link org.jsecurity.authc.pam.ModularAuthenticationStrategy ModularAuthenticationStrategy}.

+ * {@link AuthenticationStrategy}.

  */

 package org.jsecurity.authc.pam;
\ No newline at end of file
diff --git a/core/src/org/jsecurity/mgt/AuthenticatingSecurityManager.java b/core/src/org/jsecurity/mgt/AuthenticatingSecurityManager.java
index a064223..8d94565 100644
--- a/core/src/org/jsecurity/mgt/AuthenticatingSecurityManager.java
+++ b/core/src/org/jsecurity/mgt/AuthenticatingSecurityManager.java
@@ -19,7 +19,7 @@
 package org.jsecurity.mgt;
 
 import org.jsecurity.authc.*;
-import org.jsecurity.authc.pam.ModularAuthenticationStrategy;
+import org.jsecurity.authc.pam.AuthenticationStrategy;
 import org.jsecurity.authc.pam.ModularRealmAuthenticator;
 import org.jsecurity.util.LifecycleUtils;
 
@@ -89,20 +89,20 @@
     }
 
     /**
-     * Sets the {@link org.jsecurity.authc.pam.ModularAuthenticationStrategy ModularAuthenticationStrategy} to use
+     * Sets the {@link org.jsecurity.authc.pam.AuthenticationStrategy} to use
      * in multi-realm environments.
      *
-     * @param strategy the <code>ModularAuthenticationStrategy</code> to use in multi-realm environments.
+     * @param strategy the <code>AuthenticationStrategy</code> to use in multi-realm environments.
      */
-    public void setModularAuthenticationStrategy(ModularAuthenticationStrategy strategy) {
+    public void setAuthenticationStrategy(AuthenticationStrategy strategy) {
         if (!(this.authenticator instanceof ModularRealmAuthenticator)) {
-            String msg = "Configuring a ModularAuthenticationStrategy is only applicable when the underlying " +
+            String msg = "Configuring a AuthenticationStrategy is only applicable when the underlying " +
                     "Authenticator implementation is a " + ModularRealmAuthenticator.class.getName() +
                     " implementation.  This SecurityManager has been configured with an Authenticator of type " +
                     this.authenticator.getClass().getName();
             throw new IllegalStateException(msg);
         }
-        ((ModularRealmAuthenticator) this.authenticator).setModularAuthenticationStrategy(strategy);
+        ((ModularRealmAuthenticator) this.authenticator).setAuthenticationStrategy(strategy);
     }
 
     /**
@@ -130,7 +130,7 @@
     public void setAuthenticationListeners(Collection<AuthenticationListener> listeners) {
         assertAuthenticatorListenerSupport();
         if (!(this.authenticator instanceof AuthenticationListenerRegistrar)) {
-            String msg = "Configuring a ModularAuthenticationStrategy is only applicable when the underlying " +
+            String msg = "Configuring a AuthenticationStrategy is only applicable when the underlying " +
                     "Authenticator implementation is a " + AuthenticationListenerRegistrar.class.getName() +
                     " implementation.  This SecurityManager has been configured with an Authenticator of type " +
                     this.authenticator.getClass().getName() + ", which does not implement that interface.";
diff --git a/core/src/org/jsecurity/realm/SimpleAccountRealm.java b/core/src/org/jsecurity/realm/SimpleAccountRealm.java
index fdc262a..1673783 100644
--- a/core/src/org/jsecurity/realm/SimpleAccountRealm.java
+++ b/core/src/org/jsecurity/realm/SimpleAccountRealm.java
@@ -140,12 +140,16 @@
         UsernamePasswordToken upToken = (UsernamePasswordToken) token;
         SimpleAccount account = (SimpleAccount) getAuthorizationCache().get(upToken.getUsername());
 
-        if (account.isLocked()) {
-            throw new LockedAccountException("Account [" + account + "] is locked.");
-        }
-        if (account.isCredentialsExpired()) {
-            String msg = "The credentials for account [" + account + "] are expired";
-            throw new ExpiredCredentialsException(msg);
+        if( account != null ) {
+
+            if (account.isLocked()) {
+                throw new LockedAccountException("Account [" + account + "] is locked.");
+            }
+            if (account.isCredentialsExpired()) {
+                String msg = "The credentials for account [" + account + "] are expired";
+                throw new ExpiredCredentialsException(msg);
+            }
+            
         }
 
         return account;
diff --git a/core/src/org/jsecurity/realm/jdbc/JdbcRealm.java b/core/src/org/jsecurity/realm/jdbc/JdbcRealm.java
index 4daaf55..5768f4b 100644
--- a/core/src/org/jsecurity/realm/jdbc/JdbcRealm.java
+++ b/core/src/org/jsecurity/realm/jdbc/JdbcRealm.java
@@ -274,7 +274,9 @@
 

             // Retrieve roles and permissions from database

             roleNames = getRoleNamesForUser(conn, username);

-            permissions = getPermissions(conn, username, roleNames);

+            if( permissionsLookupEnabled ) {

+                permissions = getPermissions(conn, username, roleNames);

+            }

 

         } catch (SQLException e) {

             final String message = "There was a SQL error while authorizing user [" + username + "]";

diff --git a/core/src/org/jsecurity/realm/text/TextConfigurationRealm.java b/core/src/org/jsecurity/realm/text/TextConfigurationRealm.java
index 5762897..b9a5488 100644
--- a/core/src/org/jsecurity/realm/text/TextConfigurationRealm.java
+++ b/core/src/org/jsecurity/realm/text/TextConfigurationRealm.java
@@ -205,7 +205,9 @@
         Map<String, String> pairs = new HashMap<String, String>();
         for (String pairString : keyValuePairs) {
             String[] pair = StringUtils.splitKeyValue(pairString);
-            pairs.put(pair[0].trim(), pair[1].trim());
+            if( pair != null ) {
+                pairs.put(pair[0].trim(), pair[1].trim());
+            }
         }
 
         return pairs;
diff --git a/core/test/org/jsecurity/authc/pam/AllSuccessfulModularAuthenticationStrategyTest.java b/core/test/org/jsecurity/authc/pam/AllSuccessfulStrategyTest.java
similarity index 90%
rename from core/test/org/jsecurity/authc/pam/AllSuccessfulModularAuthenticationStrategyTest.java
rename to core/test/org/jsecurity/authc/pam/AllSuccessfulStrategyTest.java
index 65dc10d..5442c64 100644
--- a/core/test/org/jsecurity/authc/pam/AllSuccessfulModularAuthenticationStrategyTest.java
+++ b/core/test/org/jsecurity/authc/pam/AllSuccessfulStrategyTest.java
@@ -26,17 +26,17 @@
 import org.jsecurity.realm.Realm;
 import org.jsecurity.realm.SimpleAccountRealm;
 import org.jsecurity.subject.PrincipalCollection;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Test;
 
-public class AllSuccessfulModularAuthenticationStrategyTest {
+public class AllSuccessfulStrategyTest {
 
-    private AllSuccessfulModularAuthenticationStrategy strategy;
+    private AllSuccessfulStrategy strategy;
 
     @Before
     public void setUp() {
-        strategy = new AllSuccessfulModularAuthenticationStrategy();
+        strategy = new AllSuccessfulStrategy();
     }
 
     @Test
diff --git a/ivy.xml b/ivy.xml
index 945c03b..3f2cd37 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -71,6 +71,10 @@
         <dependency org="javax.servlet" name="jstl" rev="1.2" transitive="false" conf="samples"/>
         <dependency org="taglibs" name="standard" rev="1.1.2" transitive="false" conf="samples"/>
         <dependency org="hsqldb" name="hsqldb" rev="1.8.0.7" transitive="false" conf="samples"/>
+        <dependency org="org.hibernate" name="ejb3-persistence" rev="1.0.2.GA" conf="samples"/>
+        <dependency org="org.hibernate" name="hibernate-annotations" rev="3.2.1.ga" conf="samples">
+            <exclude org="javax.transaction"/>
+        </dependency>
         <dependency org="org.hibernate" name="hibernate" rev="3.2.6.ga" conf="samples">
             <!-- JTA 1.0.1b jar is not allowed in the Maven repo because of Sun's binary license, so we have to
                  download it explictly from another location (we use Geronimo's version) -->
@@ -87,99 +91,6 @@
             <artifact name="docbook-libs" type="zip" ext="zip"/>
         </dependency>
 
-        <!-- TODO: move these contents (old library_versions.txt file) into appropriate places in this document. -->
-        <!-- This file lists all 3rd party libraries (and their versions!!!) that
-        are required to build or run the project.
-
-        If you're a developer editing this file, please keep all listings in alphabetical order for
-        convenient lookup.
-
-        Some notes:
-
-        - Each 3rd party library is stored in a directory named after the
-          open-source project or company that created it.  This makes for
-          easy organization of lots of libs.
-
-        - Jar files stored _do not_ have version numbers
-          actually in their file name.  If the jar originally comes as such,
-          it is renamed with the version number stripped off.  The version
-          number is instead maintained in this file.  The reason for this is
-          so that every time we need to upgrade any 3rd party library,
-          we don't have to edit any build.xml or properies files to reflect
-          a potential name change due to a version number - you just note
-          that change once in this file and overwrite the old jar, checking
-          in that overwrite to CVS in the process.
-
-        - Each 3rd party lib is listed here along with what it is
-          used for and if its needed for build-time, run-time or both.
-
-        - Thanks to the gents working on the Spring Framework (http://www.springframework.org)
-          that provided the template and the idea for this file.
-
-        * atunit/atunut.jar
-        - AtUnit 1.0
-        - Used in unit tests
-
-        * easymock/easymock.jar, easymockclassextension.jar
-        - EasyMock 2.2 w/ ClassExtension 2.2 (http://www.easymock.org)
-        - Used in test cases for creating dynamic mock objects
-
-        * ehcache/ehcache.jar
-        - ehcache 1.3.0 (http://ehcache.sourceforge.net/)
-        - Required for compiling, using ehcache cache manager, and for runtime session management
-
-        * google-collections/google-collect.jar
-        - Google Collections Snapshot 20071022
-        - Used in test cases for conveniently constructing collections
-
-        * hsqldb/hsqldb.jar
-        - HSQLDB 1.8.0.7 (http://www.hsqldb.org)
-        - Used in the sample application to show an example of a JDBC-based Realm.
-
-        * j2ee/jsp-api.jar
-        - JSP API 2.0 (http://java.sun.com/products/jsp)
-        - Required for building the RI tag libraries
-
-        * j2ee/servlet-api.jar
-        - Servlet API 2.4 (http://java.sun.com/products/servlet)
-        - required for building web support classes (Servlet Filters, etc)
-
-        * jakarta-commons/commons-beanutils-core.jar
-        - Commons BeanUtils 1.7.0 (http://jakarta.apache.org/commons/beanutils/index.html)
-        - required for building and running JSecurity
-        - used for constructing Permissions in the Reference Implementation's PermissionAnnotationAuthorizationModule
-
-        * jboss/jboss-aop.jar
-        - JBoss AOP jar from the JBoss 4.0.4.GA Application Server release
-        - Required at build time for JBoss integration support classes.
-
-        * jug/jug.jar
-        - Java Uuid Genrator (JUG) v. 2.0.0 (Apache 2.0 jar) (http://jug.safehaus.org/)
-        - used as a fallback UUID generator for memory-based session management if below Java 1.5
-
-        * junit/junit.jar
-        - JUnit Test Framework 4.1 (http://www.junit.org)
-        - Required for building and running test cases
-
-        * log4j/log4j.jar
-        - Log4J 1.2.9 (http://logging.apache.org/log4j)
-        - required by the RI during build and runtime for logging support
-
-        * quartz/quartz.jar
-        - Quartz 1.5.2 (http://www.opensymphony.com/quartz)
-        - required during build and runtime for Quartz-based session validation support
-
-        * retroweaver/retroweaver.jar,retroweaver-rt.jar
-        - Retroweaver 2.0 (http://retroweaver.sourceforge.net)
-        - required for retroweaving to support JDK 1.3 and 1.4
-        - rt jar is required at runtime if running in 1.3 or 1.4
-
-        * spring/spring.jar
-        - Spring Application Framework 2.0.2 (http://springframework.org)
-        - required to build Spring integration support and sample apps
-        -->
-
-
     </dependencies>
 
 </ivy-module>
\ No newline at end of file
diff --git a/samples/spring-hibernate/build.xml b/samples/spring-hibernate/build.xml
index 33713d9..74d3a3f 100644
--- a/samples/spring-hibernate/build.xml
+++ b/samples/spring-hibernate/build.xml
@@ -56,9 +56,11 @@
     <target name="compile" depends="compile.src"/> <!-- no test classes, no need to depend on compile.test -->
 
     <target name="war">
-        <war warfile="${dist.war}" webxml="WEB-INF/web.xml">
+            <echo message="DIST DIR: ${dist.dir}"/>
+        <war warfile="${dist.war}" webxml="web/WEB-INF/web.xml">
             <lib dir="${dist.dir}" includes="*.jar"/>
-            <lib dir="${root.dist.dir}" includes="jsecurity.jar"/>
+            <lib dir="${dist.dir}/samples" includes="jsecurity-samples-sprhib*.jar"/>
+            <lib dir="${dist.dir}/modules" includes="*.jar"/>
             <lib dir="${lib.dir}/samples">
                 <include name="jstl-*.jar"/>
                 <include name="standard-*.jar"/>
@@ -72,15 +74,16 @@
             <!-- Hibernate libs: -->
             <lib dir="${lib.dir}/samples">
                 <include name="hibernate-*.jar"/>
+                <include name="persistence-api-*.jar"/>
                 <include name="antlr-*.jar"/>
                 <include name="asm-attrs-*.jar"/>
                 <include name="asm-*.jar"/>
                 <include name="cglib-*.jar"/>
-                <include name="geronimo-jta_1.0.1B_spec.jar"/>
+                <include name="geronimo-jta_1.0.1B_spec-*.jar"/>
                 <include name="dom4j-*.jar"/>
                 <include name="commons-collections-*.jar"/>
             </lib>
-            <fileset dir="${base.dir}" includes="**" excludes="**/web.xml, **/build.xml"/>
+            <fileset dir="${base.dir}/web" includes="**" excludes="**/web.xml, **/build.xml"/>
         </war>
 
     </target>
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/BootstrapDataPopulator.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/BootstrapDataPopulator.java
new file mode 100644
index 0000000..4f90700
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/BootstrapDataPopulator.java
@@ -0,0 +1,63 @@
+/*
+ * 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.jsecurity.samples.sprhib.dao;
+
+import org.hibernate.SessionFactory;
+import org.jsecurity.crypto.hash.Sha256Hash;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+
+/**
+ * Loads sample data for the sample app since it's an in-memory database.
+ */
+@Component
+public class BootstrapDataPopulator implements InitializingBean {
+
+    private DataSource dataSource;
+    private SessionFactory sessionFactory;
+
+    @Autowired
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+    
+    // Session factory is only injected to ensure it is initialized before this runs
+    @Autowired
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        //because we're using an in-memory hsqldb for the sample app, a new one will be created each time the
+        //app starts, so insert the sample admin user at startup:
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
+
+        jdbcTemplate.execute( "insert into roles values (1, 'user', 'The default role given to all users.')" );
+        jdbcTemplate.execute( "insert into roles values (2, 'admin', 'The administrator role only given to site admins')" );
+        jdbcTemplate.execute( "insert into roles_permissions values (2, 'user:*')" );
+        jdbcTemplate.execute( "insert into users(id,username,email,password) values (1, 'admin', 'sample@jsecurity.org', '" + new Sha256Hash("admin").toHex() + "')" );
+        jdbcTemplate.execute( "insert into users_roles values (1, 2)" );
+        
+
+    }
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/HibernateDao.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/HibernateDao.java
new file mode 100644
index 0000000..65aedd5
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/HibernateDao.java
@@ -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.jsecurity.samples.sprhib.dao;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.orm.hibernate3.SessionFactoryUtils;
+
+/**
+ * Convenience superclass for DAOs that contains annotations for injecting the session factory
+ * and accessing the session.
+ */
+public abstract class HibernateDao {
+
+    private SessionFactory sessionFactory;
+
+    @Autowired
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    public Session getSession() {
+        return SessionFactoryUtils.getSession(this.sessionFactory, true);
+    }    
+
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/HibernateUserDAO.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/HibernateUserDAO.java
new file mode 100644
index 0000000..9107ca5
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/HibernateUserDAO.java
@@ -0,0 +1,61 @@
+/*
+ * 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.jsecurity.samples.sprhib.dao;
+
+import org.jsecurity.samples.sprhib.model.User;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.Assert;
+
+import java.util.List;
+
+@Repository("userDAO")
+@SuppressWarnings("unchecked")
+public class HibernateUserDAO extends HibernateDao implements UserDAO {
+
+    public User getUser(Long userId) {
+        return (User) getSession().get(User.class, userId);
+    }
+
+    public User findUser(String username) {
+        Assert.hasText(username);
+        String query = "from User u where u.username = :username";
+        return (User) getSession().createQuery(query).setString("username", username).uniqueResult();
+    }
+
+    public void createUser(User user) {
+        getSession().save( user );
+    }
+
+    public List<User> getAllUsers() {
+        return getSession().createQuery("from User order by username").list();
+    }
+
+    public void deleteUser(Long userId) {
+        User user = getUser(userId);
+        if( user != null ) {
+            getSession().delete(user);
+        }
+    }
+
+    public void updateUser(User user) {
+        getSession().update(user);
+    }
+
+}
+
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/eis/UserDAO.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/UserDAO.java
similarity index 68%
rename from samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/eis/UserDAO.java
rename to samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/UserDAO.java
index ef42eea..a6a7ca5 100644
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/eis/UserDAO.java
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/dao/UserDAO.java
@@ -16,20 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.jsecurity.samples.sprhib.party.eis;
+package org.jsecurity.samples.sprhib.dao;
 
-import org.jsecurity.samples.sprhib.eis.PrimaryClassDAO;
-import org.jsecurity.samples.sprhib.party.User;
+import org.jsecurity.samples.sprhib.model.User;
+
+import java.util.List;
 
 /**
- * @author Les Hazlewood
+ * Data Access Object for User related operations.
  */
-public interface UserDAO extends PrimaryClassDAO {
+public interface UserDAO {
 
-    public User getUser(Long userId);
+    User getUser(Long userId);
 
-    public User findUser(String username);
+    User findUser(String username);
 
-    //public User findUserByEmail( String email );
+    void createUser(User user);
 
+    List<User> getAllUsers();
+
+    void deleteUser(Long userId);
+
+    void updateUser(User user);
 }
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/DataAccessObject.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/DataAccessObject.java
deleted file mode 100644
index 1e5e0e0..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/DataAccessObject.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.eis;
-
-import java.io.Serializable;
-import java.util.Collection;
-
-/**
- * Behavioral specification for any object specializing in accessing, updating, and storing data in
- * an EIS.  The methods are common to all forms of EIS's, not just relational databases, meaning
- * this interface should be implemented by <i>all</i> DataAccessObject implementations, regardless
- * of the EIS technology.
- *
- * @author Les Hazlewood
- */
-public interface DataAccessObject {
-
-    /**
-     * Returns the generated ID for the given entity.
-     *
-     * @param entity the entity to create in the EIS.
-     * @return the generated id after entity creation in the EIS.
-     */
-    Serializable create(Object entity);
-
-    void update(Object entity);
-
-    void delete(Object entity);
-
-    void deleteById(Class entityType, Serializable id);
-
-    void deleteAll(Collection entities);
-}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/PrimaryClassDAO.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/PrimaryClassDAO.java
deleted file mode 100644
index 863cebf..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/PrimaryClassDAO.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.eis;
-
-import org.springframework.dao.DataAccessException;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * A convenience behavioral specification for DataAccessObjects that primarily work with one
- * Object/Class type.
- *
- * Implementations of this interface are by no means restricted to working with their primary type
- * only; indeed they may &quot;know&quot; about any Class needed to accomplish their tasks.  This
- * interface is merely intended to specify convenience behaviors common to all DAO's that
- * <i>mostly</i> work with one data type.
- *
- * @author Les Hazlewood
- */
-public interface PrimaryClassDAO extends DataAccessObject {
-
-    /**
-     * Returns the primary class for which this DAO is responsible. Most Data Access Objects will
-     * work on behalf of a specific object type (e.g. User, Post, etc.).  Implementations of this
-     * interface may of course work with any other classes as needed, but a PrimaryClassDAO always
-     * has one primary target class it &quot;knows&quot; about for convenience.
-     *
-     * @return the primary class this DAO works with.
-     */
-    Class getPrimaryClass();
-
-    /**
-     * Reads/retrieves the object of type <code>getPrimaryClass()</code> with the specified id.
-     *
-     * @param entityId the id identifying the object to retrieve.
-     * @return the object with the given entityId
-     * @throws org.springframework.dao.DataAccessException
-     *          if there is an error accessing the EIS, or if no object of type
-     *          <code>getPrimaryClass()</code> with an id of <code>entityId</code> exists in the
-     *          EIS.
-     */
-    Object read(Serializable entityId) throws DataAccessException;
-
-    /**
-     * Retrieves all instances of type <code>getPrimaryClass()</code> found in the EIS.  Use this
-     * method judiciously as a very large result set will no doubt incur a performance penalty.
-     *
-     * @return a List of instances of type <code>getPriamaryClass()</code> found in the EIS.
-     * @throws DataAccessException if an error occurs accessing the EIS.
-     */
-    List readAll() throws DataAccessException;
-
-    /**
-     * Deletes/Removes the entity of type <code>getPrimaryClass()</code> in the EIS identified by
-     * <code>entityId</code>.  Cascading deletes of other objects may be performed by the EIS if the
-     * EIS is configured to do so.
-     *
-     * @param entityId the EIS id of the record to delete.
-     * @throws DataAccessException if there is an error accessing the EIS.
-     */
-    void deleteById(Serializable entityId) throws DataAccessException;
-
-    /**
-     * Deletes <b>all</b> instances of type <code>getPrimaryClass()</code> found in the EIS.
-     * Cascading deletes of other objects may be performed by the EIS if the EIS is configured to do
-     * so.  <b>Only use this method if you're sure you want to delete all objects of the primary
-     * type</b>.
-     *
-     * @throws DataAccessException if there is an error accessing the EIS.
-     */
-    void deleteAll() throws DataAccessException;
-
-}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/GenericEnumUserType.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/GenericEnumUserType.java
deleted file mode 100644
index 119a47b..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/GenericEnumUserType.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.eis.hibernate;
-
-import org.hibernate.HibernateException;
-import org.hibernate.type.NullableType;
-import org.hibernate.type.TypeFactory;
-import org.hibernate.usertype.EnhancedUserType;
-import org.hibernate.usertype.ParameterizedType;
-
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Properties;
-
-/**
- * Implements a generic enum user type identified / represented by a single identifier / column.
- *
- *
- * <ul> <li>The enum type being represented by the certain user type must be set by using the
- * 'enumClass' property.</li> <li>The identifier representing a enum value is retrieved by the
- * identifierMethod. The name of the identifier method can be specified by the 'identifierMethod'
- * property and by default the name() method is used.</li> <li>The identifier type is automatically
- * determined by the return-type of the identifierMethod.</li> <li>The valueOfMethod is the name of
- * the static factory method returning the enumeration object being represented by the given
- * indentifier. The valueOfMethod's name can be specified by setting the 'valueOfMethod' property.
- * The default valueOfMethod's name is 'valueOf'.</li> </ul> </p> <p>Example of an enum type
- * represented by an int value:
- * <pre><code>
- * public enum SimpleNumber {
- *     Unknown(-1), Zero(0), One(1), Two(2), Three(3);
- *
- *     public int toInt() { return value; }
- *
- *     public SimpleNumber fromInt(int value) {
- *         switch(value) {
- *             case 0: return Zero;
- *             case 1: return One;
- *             case 2: return Two;
- *             case 3: return Three;
- *             default: return Unknown;
- *         }
- *     }
- * }</code></pre>
- *
- * <p>The Mapping would look like this:
- * <pre><code>
- * &lt;typedef name=&quot;SimpleNumber&quot; class=&quot;GenericEnumUserType&quot;&gt;
- * &lt;param name="enumClass">SimpleNumber&lt;/param&gt;
- * &lt;param name="identifierMethod">toInt&lt;/param&gt;
- * &lt;param name="valueOfMethod">fromInt&lt;/param&gt;
- * &lt;/typedef&gt;
- * &lt;class ...&gt;
- * ...
- * &lt;property name="number" column="number" type="SimpleNumber"/&gt;
- * &lt;/class&gt;</code></pre>
- *
- * @author Martin Kersten
- * @author Les Hazlewood
- * @since 05.05.2005
- */
-@SuppressWarnings(value = "unchecked")
-public class GenericEnumUserType implements EnhancedUserType, ParameterizedType {
-
-    private Class<? extends Enum> enumClass;
-
-    private Method identifierMethod;
-    private Method valueOfMethod;
-
-    private static final String defaultIdentifierMethodName = "name";
-    private static final String defaultValueOfMethodName = "valueOf";
-
-    private static final Class[] NULL_CLASS_VARARG = null;
-    private static final Object[] NULL_OBJECT_VARARG = null;
-    private static final char SINGLE_QUOTE = '\'';
-
-    private NullableType type;
-    private int[] sqlTypes;
-
-    public void setParameterValues(Properties parameters) {
-        String enumClassName = parameters.getProperty("enumClass");
-        try {
-            enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
-        }
-        catch (ClassNotFoundException exception) {
-            throw new HibernateException("Enum class not found", exception);
-        }
-
-        String identifierMethodName =
-                parameters.getProperty("identifierMethod", defaultIdentifierMethodName);
-
-        Class<?> identifierType;
-        try {
-            identifierMethod = enumClass.getMethod(identifierMethodName, NULL_CLASS_VARARG);
-            identifierType = identifierMethod.getReturnType();
-        }
-        catch (Exception exception) {
-            throw new HibernateException("Failed to obtain identifier method", exception);
-        }
-
-        type = (NullableType) TypeFactory.basic(identifierType.getName());
-
-        if (type == null) {
-            throw new HibernateException("Unsupported identifier type " + identifierType.getName());
-        }
-
-        sqlTypes = new int[]{type.sqlType()};
-
-        String valueOfMethodName =
-                parameters.getProperty("valueOfMethod", defaultValueOfMethodName);
-
-        try {
-            valueOfMethod = enumClass.getMethod(valueOfMethodName, identifierType);
-        }
-        catch (Exception exception) {
-            throw new HibernateException("Failed to obtain valueOf method", exception);
-        }
-    }
-
-    public Class returnedClass() {
-        return enumClass;
-    }
-
-    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
-            throws HibernateException, SQLException {
-        Object identifier = type.get(rs, names[0]);
-        if (identifier == null || rs.wasNull()) {
-            return null;
-        }
-        try {
-            return valueOfMethod.invoke(enumClass, identifier);
-        } catch (Exception exception) {
-            String msg = "Exception while invoking valueOfMethod [" + valueOfMethod.getName() +
-                    "] of Enum class [" + enumClass.getName() + "] with argument of type [" +
-                    identifier.getClass().getName() + "], value=[" + identifier + "]";
-            throw new HibernateException(msg, exception);
-        }
-    }
-
-    public void nullSafeSet(PreparedStatement st, Object value, int index)
-            throws HibernateException, SQLException {
-        if (value == null) {
-            st.setNull(index, sqlTypes[0]);
-        } else {
-            try {
-                Object identifier = identifierMethod.invoke(value, NULL_OBJECT_VARARG);
-                type.set(st, identifier, index);
-            } catch (Exception exception) {
-                String msg = "Exception while invoking identifierMethod [" + identifierMethod.getName() +
-                        "] of Enum class [" + enumClass.getName() +
-                        "] with argument of type [" + value.getClass().getName() + "], value=[" + value + "]";
-                throw new HibernateException(msg, exception);
-            }
-        }
-    }
-
-    public int[] sqlTypes() {
-        return sqlTypes;
-    }
-
-    public Object assemble(Serializable cached, Object owner) throws HibernateException {
-        return cached;
-    }
-
-    public Object deepCopy(Object value) throws HibernateException {
-        return value;
-    }
-
-    public Serializable disassemble(Object value) throws HibernateException {
-        return (Serializable) value;
-    }
-
-    public String objectToSQLString(Object value) {
-        return SINGLE_QUOTE + ((Enum) value).name() + SINGLE_QUOTE;
-    }
-
-    public String toXMLString(Object value) {
-        return ((Enum) value).name();
-    }
-
-    public Object fromXMLString(String xmlValue) {
-        return Enum.valueOf(enumClass, xmlValue);
-    }
-
-    public boolean equals(Object x, Object y) throws HibernateException {
-        return x == y;
-    }
-
-    public int hashCode(Object x) throws HibernateException {
-        return x.hashCode();
-    }
-
-    public boolean isMutable() {
-        return false;
-    }
-
-    public Object replace(Object original, Object target, Object owner)
-            throws HibernateException {
-        return original;
-    }
-}
-
-
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/HibernateDAO.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/HibernateDAO.java
deleted file mode 100644
index 75bc498..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/HibernateDAO.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.eis.hibernate;
-
-import org.hibernate.Filter;
-import org.hibernate.LockMode;
-import org.hibernate.ReplicationMode;
-import org.hibernate.criterion.DetachedCriteria;
-import org.jsecurity.samples.sprhib.eis.DataAccessObject;
-import org.springframework.dao.DataAccessException;
-import org.springframework.orm.hibernate3.HibernateCallback;
-import org.springframework.orm.hibernate3.HibernateOperations;
-import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Simplifies hibernate usage and enhances readability for subclasses so they don't have to call
- * <code>getHibernateTemplate()</code> for every Hibernate operation.
- *
- * @author Les Hazlewood
- */
-public class HibernateDAO extends HibernateDaoSupport
-        implements HibernateOperations, DataAccessObject {
-
-    public Object execute(HibernateCallback action) throws DataAccessException {
-        return getHibernateTemplate().execute(action);
-    }
-
-    public List executeFind(HibernateCallback action) throws DataAccessException {
-        return getHibernateTemplate().executeFind(action);
-    }
-
-    public Object get(Class entityClass, Serializable id) throws DataAccessException {
-        return getHibernateTemplate().get(entityClass, id);
-    }
-
-    public Object get(Class entityClass, Serializable id, LockMode lockMode)
-            throws DataAccessException {
-        return getHibernateTemplate().get(entityClass, id, lockMode);
-    }
-
-    public Object get(String entityName, Serializable id) throws DataAccessException {
-        return getHibernateTemplate().get(entityName, id);
-    }
-
-    public Object get(String entityName, Serializable id, LockMode lockMode)
-            throws DataAccessException {
-        return getHibernateTemplate().get(entityName, id, lockMode);
-    }
-
-    public Object load(Class entityClass, Serializable id) throws DataAccessException {
-        return getHibernateTemplate().load(entityClass, id);
-    }
-
-    public Object load(Class entityClass, Serializable id, LockMode lockMode)
-            throws DataAccessException {
-        return getHibernateTemplate().load(entityClass, id, lockMode);
-    }
-
-    public Object load(String entityName, Serializable id) throws DataAccessException {
-        return getHibernateTemplate().load(entityName, id);
-    }
-
-    public Object load(String entityName, Serializable id, LockMode lockMode)
-            throws DataAccessException {
-        return getHibernateTemplate().load(entityName, id, lockMode);
-    }
-
-    public List loadAll(Class entityClass) throws DataAccessException {
-        return getHibernateTemplate().loadAll(entityClass);
-    }
-
-    public void load(Object entity, Serializable id) throws DataAccessException {
-        getHibernateTemplate().load(entity, id);
-    }
-
-    public void refresh(Object entity) throws DataAccessException {
-        getHibernateTemplate().refresh(entity);
-    }
-
-    public void refresh(Object entity, LockMode lockMode) throws DataAccessException {
-        getHibernateTemplate().refresh(entity, lockMode);
-    }
-
-    public boolean contains(Object entity) throws DataAccessException {
-        return getHibernateTemplate().contains(entity);
-    }
-
-    public void evict(Object entity) throws DataAccessException {
-        getHibernateTemplate().evict(entity);
-    }
-
-    public void initialize(Object proxy) throws DataAccessException {
-        getHibernateTemplate().initialize(proxy);
-    }
-
-    public Filter enableFilter(String string) throws IllegalStateException {
-        return getHibernateTemplate().enableFilter(string);
-    }
-
-    public void lock(Object entity, LockMode lockMode) throws DataAccessException {
-        getHibernateTemplate().lock(entity, lockMode);
-    }
-
-    public void lock(String entityName, Object entity, LockMode lockMode)
-            throws DataAccessException {
-        getHibernateTemplate().lock(entityName, entity, lockMode);
-    }
-
-    public Serializable create(Object entity) {
-        return save(entity);
-    }
-
-    public Serializable save(Object entity) throws DataAccessException {
-        return getHibernateTemplate().save(entity);
-    }
-
-    public Serializable save(String entityName, Object entity) throws DataAccessException {
-        return getHibernateTemplate().save(entityName, entity);
-    }
-
-    public void update(Object entity) throws DataAccessException {
-        getHibernateTemplate().update(entity);
-    }
-
-    public void update(Object entity, LockMode lockMode) throws DataAccessException {
-        getHibernateTemplate().update(entity, lockMode);
-    }
-
-    public void update(String entityName, Object entity) throws DataAccessException {
-        getHibernateTemplate().update(entityName, entity);
-    }
-
-    public void update(String entityName, Object entity, LockMode lockMode)
-            throws DataAccessException {
-        getHibernateTemplate().update(entityName, entity, lockMode);
-    }
-
-    public void saveOrUpdate(Object entity) throws DataAccessException {
-        getHibernateTemplate().saveOrUpdate(entity);
-    }
-
-    public void saveOrUpdate(String entityName, Object entity) throws DataAccessException {
-        getHibernateTemplate().saveOrUpdate(entityName, entity);
-    }
-
-    public void saveOrUpdateAll(Collection entities) throws DataAccessException {
-        getHibernateTemplate().saveOrUpdateAll(entities);
-    }
-
-    public void replicate(Object object, ReplicationMode replicationMode) throws DataAccessException {
-        getHibernateTemplate().replicate(object, replicationMode);
-    }
-
-    public void replicate(String string, Object object, ReplicationMode replicationMode) throws DataAccessException {
-        getHibernateTemplate().replicate(string, object, replicationMode);
-    }
-
-    public void persist(Object entity) throws DataAccessException {
-        getHibernateTemplate().persist(entity);
-    }
-
-    public void persist(String entityName, Object entity) throws DataAccessException {
-        getHibernateTemplate().persist(entityName, entity);
-    }
-
-    public Object merge(Object entity) throws DataAccessException {
-        return getHibernateTemplate().merge(entity);
-    }
-
-    public Object merge(String entityName, Object entity) throws DataAccessException {
-        return getHibernateTemplate().merge(entityName, entity);
-    }
-
-    public void delete(Object entity) throws DataAccessException {
-        getHibernateTemplate().delete(entity);
-    }
-
-    public void delete(Object entity, LockMode lockMode) throws DataAccessException {
-        getHibernateTemplate().delete(entity, lockMode);
-    }
-
-    public void delete(String s, Object o) throws DataAccessException {
-        getHibernateTemplate().delete(s, o);
-    }
-
-    public void delete(String s, Object o, LockMode lockMode) throws DataAccessException {
-        getHibernateTemplate().delete(s, o, lockMode);
-    }
-
-    public void deleteById(Class entityType, Serializable id) {
-        delete(load(entityType, id));
-    }
-
-    public void delete(Collection entities) {
-        deleteAll(entities);
-    }
-
-    public void deleteAll(Collection entities) throws DataAccessException {
-        getHibernateTemplate().deleteAll(entities);
-    }
-
-    public void flush() throws DataAccessException {
-        getHibernateTemplate().flush();
-    }
-
-    public void clear() throws DataAccessException {
-        getHibernateTemplate().clear();
-    }
-
-    public List find(String queryString) throws DataAccessException {
-        return getHibernateTemplate().find(queryString);
-    }
-
-    public List find(String queryString, Object value) throws DataAccessException {
-        return getHibernateTemplate().find(queryString, value);
-    }
-
-    public List find(String queryString, Object[] values) throws DataAccessException {
-        return getHibernateTemplate().find(queryString, values);
-    }
-
-    /**
-     * Execute a query for persistent instances expecting only a single result.  If no results
-     * are returned from the query, this method returns <tt>null</tt>.
-     *
-     * @param queryString a query expressed in Hibernate's query language
-     * @return the single object returned from the query, or <tt>null</tt> if no results were
-     *         returned from the query.
-     * @throws org.springframework.dao.IncorrectResultSizeDataAccessException
-     *          if more than 1 result is returned
-     * @throws org.springframework.dao.DataAccessException
-     *          in case of Hibernate errors
-     * @see org.hibernate.Session#createQuery
-     */
-    public Object findSingle(String queryString) throws DataAccessException {
-        return findSingle(queryString, (Object[]) null);
-    }
-
-    /**
-     * Execute a query expecting a single persistent instance, binding
-     * one value to a "?" parameter in the query string.
-     *
-     * @param queryString a query expressed in Hibernate's query language
-     * @param value       the value of the parameter
-     * @return the single object returned from the query, or <tt>null</tt> if no results were
-     *         returned from the query.
-     * @throws org.springframework.dao.IncorrectResultSizeDataAccessException
-     *          if more than 1 result is returned
-     * @throws org.springframework.dao.DataAccessException
-     *          in case of Hibernate errors
-     * @see org.hibernate.Session#createQuery
-     */
-    public Object findSingle(String queryString, Object value) {
-        return findSingle(queryString, new Object[]{value});
-    }
-
-    /**
-     * Execute a query expecting a single persistent instance, binding a
-     * number of values to "?" parameters in the query string.
-     *
-     * @param queryString a query expressed in Hibernate's query language
-     * @param values      the values of the parameters
-     * @return the single object returned from the query, or <tt>null</tt> if no results were
-     *         returned from the query.
-     * @throws org.springframework.dao.IncorrectResultSizeDataAccessException
-     *          if more than 1 result is returned
-     * @throws org.springframework.dao.DataAccessException
-     *          in case of Hibernate errors
-     * @see org.hibernate.Session#createQuery
-     */
-    public Object findSingle(String queryString, Object[] values) throws DataAccessException {
-        List results = find(queryString, values);
-
-        if (results != null && !results.isEmpty()) {
-            return results.get(0);
-        }
-
-        return null;
-    }
-
-    public List findByCriteria(DetachedCriteria criteria) throws DataAccessException {
-        return getHibernateTemplate().findByCriteria(criteria);
-    }
-
-    public List findByCriteria(DetachedCriteria criteria, int firstResult, int maxResults)
-            throws DataAccessException {
-        return getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);
-    }
-
-    public List findByExample(Object object) throws DataAccessException {
-        return getHibernateTemplate().findByExample(object);
-    }
-
-    public List findByExample(Object object, int i, int i1) throws DataAccessException {
-        return getHibernateTemplate().findByExample(object, i, i1);
-    }
-
-    public List findByExample(String entityName, Object exampleEntity) throws DataAccessException {
-        return getHibernateTemplate().findByExample(entityName, exampleEntity);
-    }
-
-    public List findByExample(String entityName, Object exampleEntity, int firstResult, int maxResults) throws DataAccessException {
-        return getHibernateTemplate().findByExample(entityName, exampleEntity, firstResult, maxResults);
-    }
-
-    public List findByNamedParam(String queryName, String paramName, Object value)
-            throws DataAccessException {
-        return getHibernateTemplate().findByNamedParam(queryName, paramName, value);
-    }
-
-    public List findByNamedParam(String queryString, String[] paramNames, Object[] values)
-            throws DataAccessException {
-        return getHibernateTemplate().findByNamedParam(queryString, paramNames, values);
-    }
-
-    public List findByValueBean(String queryString, Object valueBean) throws DataAccessException {
-        return getHibernateTemplate().findByValueBean(queryString, valueBean);
-    }
-
-    public List findByNamedQuery(String queryName) throws DataAccessException {
-        return getHibernateTemplate().findByNamedQuery(queryName);
-    }
-
-    public List findByNamedQuery(String queryName, Object value) throws DataAccessException {
-        return getHibernateTemplate().findByNamedQuery(queryName, value);
-    }
-
-    public List findByNamedQuery(String queryName, Object[] values) throws DataAccessException {
-        return getHibernateTemplate().findByNamedQuery(queryName, values);
-    }
-
-    public List findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
-            throws DataAccessException {
-        return getHibernateTemplate().findByNamedQueryAndNamedParam(queryName, paramName, value);
-    }
-
-    public List findByNamedQueryAndNamedParam(String queryName, String[] paramNames,
-                                              Object[] values) throws DataAccessException {
-        return getHibernateTemplate().findByNamedQueryAndNamedParam(queryName, paramNames, values);
-    }
-
-    public List findByNamedQueryAndValueBean(String queryName, Object valueBean)
-            throws DataAccessException {
-        return getHibernateTemplate().findByNamedQueryAndValueBean(queryName, valueBean);
-    }
-
-    public Iterator iterate(String queryString) throws DataAccessException {
-        return getHibernateTemplate().iterate(queryString);
-    }
-
-    public Iterator iterate(String queryString, Object value) throws DataAccessException {
-        return getHibernateTemplate().iterate(queryString, value);
-    }
-
-    public Iterator iterate(String queryString, Object[] values) throws DataAccessException {
-        return getHibernateTemplate().iterate(queryString, values);
-    }
-
-    public void closeIterator(Iterator it) throws DataAccessException {
-        getHibernateTemplate().closeIterator(it);
-    }
-
-    public int bulkUpdate(String string) throws DataAccessException {
-        return getHibernateTemplate().bulkUpdate(string);
-    }
-
-    public int bulkUpdate(String string, Object object) throws DataAccessException {
-        return getHibernateTemplate().bulkUpdate(string, object);
-    }
-
-    public int bulkUpdate(String string, Object[] objects) throws DataAccessException {
-        return getHibernateTemplate().bulkUpdate(string, objects);
-    }
-
-}
-
-
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/PrimaryClassHibernateDAO.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/PrimaryClassHibernateDAO.java
deleted file mode 100644
index 4aa60b5..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/eis/hibernate/PrimaryClassHibernateDAO.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.eis.hibernate;
-
-import org.jsecurity.samples.sprhib.eis.PrimaryClassDAO;
-import org.springframework.dao.DataAccessException;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * @author Les Hazlewood
- */
-public class PrimaryClassHibernateDAO extends HibernateDAO
-        implements PrimaryClassDAO {
-
-    protected Class primaryClass;
-
-    public final Class getPrimaryClass() {
-        return primaryClass;
-    }
-
-    public final void setPrimaryClass(Class clazz) {
-        primaryClass = clazz;
-    }
-
-    protected final void checkDaoConfiguration() throws Exception {
-        if (getPrimaryClass() == null) {
-            String msg = "Primary class property must be set";
-            throw new IllegalArgumentException(msg);
-        }
-    }
-
-    public Object read(Serializable entityId) throws DataAccessException {
-        return load(getPrimaryClass(), entityId);
-    }
-
-
-    public final List readAll() throws DataAccessException {
-        return loadAll(getPrimaryClass());
-    }
-
-
-    public void deleteById(final Serializable id) throws DataAccessException {
-        deleteById(getPrimaryClass(), id);
-    }
-
-    public void deleteAll() throws DataAccessException {
-        deleteAll(readAll());
-    }
-
-}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/entity/Entity.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/entity/Entity.java
deleted file mode 100644
index 27c3573..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/entity/Entity.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.entity;
-
-import java.io.Serializable;
-
-/**
- * Root parent class for all persistent object entities.
- *
- * <p><b>NOTE:</b>  Subclasses should <b>always</b> provide a unique
- * onEquals() and hashCode() implementation and these should <em>not</em> use
- * the {@link #getId id} property.  Always keep in mind the subclass' 'business keys' aka 'natural keys'
- * when implementing these two methods.
- *
- * <p>This class was borrowed from the <a href="http://code.google.com/p/pojodm">PojoDM Project</a>'s
- * <a href="http://code.google.com/p/pojodm/source/browse/trunk/src/org/pojodm/entity/Entity.java">Entity.java</a>
- * for a quick kickstart.</p>
- *
- * @author Les Hazlewood
- */
-public abstract class Entity implements Identifiable, Serializable, Cloneable {
-
-    /**
-     * RDBMS Primary key, aka 'surrogate key'.  <code>Long</code> surrogate keys are best
-     * for RDBMS performance (for many reasons that can't be expanded on here)
-     * But, every single table should _always_ have a 'business key' or 'natural key' - a unique
-     * constraint across one or more columns that guarantee row duplicates will
-     * never occur. A <code>null</code> value means the object hasn't been persisted to the RDBMS
-     */
-    protected Long id = null;
-
-    /**
-     * Used for optimistic locking to ensure two threads (even across different machines)
-     * don't simultaneously overwrite entity state.  This propert is not necessarily used by all subclasses, but
-     * it is pretty much required if in a high-concurrency environment and/or if using distributed
-     * caching in a cluster.   It (and its corresponding mutator methods) is not called
-     * 'version' to prevent eliminating that name from subclasses should the business
-     * domain naming conventions require it.  Also 'entityVersion' is self-documenting
-     * and leaves little room for incorrect interpretation.
-     */
-    protected int entityVersion = -1;
-
-    public Entity() {
-    }
-
-    public Long getId() {
-        return this.id;
-    }
-
-    /**
-     * <p>Should <em>never</em> be called directly.  Only via JPA or Hibernate or other EIS framework, since
-     * they get the ID from the RDBMS directly.</p>
-     *
-     * <p>This method can be removed entirely if the EIS framework supports setting the ID property
-     * directly (e.g. through reflection).  Hibernate does support this, it is called 'property access'.</p>
-     *
-     * @param id the entity id
-     */
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public int getEntityVersion() {
-        return this.entityVersion;
-    }
-
-    /**
-     * For the same reasons as the setId() method, this should only be called by a
-     * framework and never directly.  Can be removed if the framework supports property access.
-     *
-     * @param entityVersion the entity version number
-     */
-    public void setEntityVersion(int entityVersion) {
-        this.entityVersion = entityVersion;
-    }
-
-    /**
-     * This method is declared final and does a lot of performance optimization:
-     *
-     * <p>It delegates the actual "equals" check to subclasses via the onEquals method, but
-     * it will only do so if the object for equality comparison is</p>
-     *
-     * <ol>
-     * <li>not the same memory location as the current object (fast sanity check)</li>
-     * <li>is <code>instanceof</code> Entity</li>
-     * <li>Does not have the same id() property</li>
-     * </ol>
-     *
-     * <p>#3 is important:  this is because if two different entities have the ID property
-     * already populated, then they have already been inserted in the database, and
-     * because of unique constraints on the database (i.e. your 'business key'), you
-     * can <em>guarantee</em> that the objects are not the same and there is no need
-     * to incur the (sometimes costly) attribute-based comparisons for equals() checks.</p>
-     *
-     * <p>This little technique is a big performance improvement given the number of times
-     * equals checks happen in most applications.</p>
-     */
-    public final boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-
-        if (o instanceof Entity) {
-            Entity e = (Entity) o;
-            Long thisId = getId();
-            Long otherId = e.getId();
-            if (thisId != null && otherId != null) {
-                return thisId.equals(otherId) && getClass().equals(e.getClass());
-            } else {
-                return onEquals(e);
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Subclasses must do an equals comparison based on business keys, aka 'natural keys' here.  Do <em>NOT</em> use
-     * the {@link #getId id} property in these checks.
-     *
-     * @param e the Entity to check for &quot;business&quot; equality based on natural keys.
-     * @return <code>true</code> if the specified Entity is naturally equal to this Entity, <code>false</code> otherwise.
-     */
-    public abstract boolean onEquals(Entity e);
-
-    public abstract int hashCode();
-
-    /**
-     * If children classes override this method they must always call super.clone() to get the object
-     * with which they manipulate further to clone remaining attributes.  Never acquire
-     * the cloned object directly via 'new' operator (this is true in Java for any class - it is not special to
-     * this Entity class).
-     */
-    @Override
-    @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
-    public Object clone() {
-
-        Entity e;
-        try {
-            e = (Entity) super.clone();
-        } catch (CloneNotSupportedException neverHappens) {
-            // Should _never_ happen since this class is Cloneable and
-            // a direct subclass of Object
-            throw new InternalError("Unable to clone object of type [" + getClass().getName() + "]");
-        }
-
-        e.setId(null);
-        e.setEntityVersion(-1);
-        return e;
-    }
-
-    /**
-     * Returns a StringBuffer representing the toString function of the class implementation. This
-     * should be overridden by all children classes to represent the object in a meaningful String format.
-     *
-     * @return a <tt>StringBuffer</tt> reperesenting the <tt>toString</tt> value of this object.
-     */
-    public StringBuffer toStringBuffer() {
-        return new StringBuffer(super.toString());
-    }
-
-    /**
-     * Returns toStringBuffer().toString().  Declared as 'final' to require subclasses to override
-     * the {@link #toStringBuffer()} method, a cleaner and better performing mechanism for toString();
-     *
-     * @return toStringBuffer().toString()
-     */
-    public final String toString() {
-        return toStringBuffer().toString();
-    }
-}
-
-
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/model/Role.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/model/Role.java
new file mode 100644
index 0000000..766d889
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/model/Role.java
@@ -0,0 +1,97 @@
+/*
+ * 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.jsecurity.samples.sprhib.model;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.annotations.CollectionOfElements;
+import org.hibernate.annotations.Index;
+
+import javax.persistence.*;
+import java.util.Set;
+
+/**
+ * Model object that represents a security role.
+ */
+@Entity
+@Table(name="roles")
+@Cache(usage= CacheConcurrencyStrategy.READ_WRITE)
+public class Role {
+
+    private Long id;
+
+    private String name;
+
+    private String description;
+
+    private Set<String> permissions;
+
+    protected Role() {
+    }
+
+    public Role(String name) {
+        this.name = name;
+    }
+
+
+    @Id
+    @GeneratedValue
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    @Basic(optional=false)
+    @Column(length=100)
+    @Index(name="idx_roles_name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Basic(optional=false)
+    @Column(length=255)
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @CollectionOfElements
+    @JoinTable(name="roles_permissions")
+    @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
+    public Set<String> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions) {
+        this.permissions = permissions;
+    }
+
+}
+
+
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/model/User.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/model/User.java
new file mode 100644
index 0000000..e0ba82f
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/model/User.java
@@ -0,0 +1,114 @@
+/*
+ * 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.jsecurity.samples.sprhib.model;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.annotations.Index;
+
+import javax.persistence.*;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Simple class that represents any User domain entity in any application.
+ *
+ * <p>Because this class performs its own Realm and Permission checks, and these can happen frequently enough in a
+ * production application, it is highly recommended that the internal User {@link #getRoles} collection be cached
+ * in a 2nd-level cache when using JPA and/or Hibernate.  The hibernate xml configuration for this sample application
+ * does in fact do this for your reference (see User.hbm.xml - the 'roles' declaration).</p>
+ */
+@Entity
+@Table(name="users")
+@Cache(usage= CacheConcurrencyStrategy.READ_WRITE)
+public class User  {
+
+    private Long id;
+    private String username;
+    private String email;
+    private String password;
+    private Set<Role> roles = new HashSet<Role>();
+
+
+    @Id
+    @GeneratedValue
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the username associated with this user account;
+     *
+     * @return the username associated with this user account;
+     */
+    @Basic(optional=false)
+    @Column(length=100)
+    @Index(name="idx_users_username")
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    @Basic(optional=false)
+    @Index(name="idx_users_email")
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    /**
+     * Returns the password for this user.
+     *
+     * @return this user's password
+     */
+    @Basic(optional=false)
+    @Column(length=255)
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+
+    @ManyToMany
+    @JoinTable(name="users_roles")
+    @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
+    public Set<Role> getRoles() {
+        return roles;
+    }
+
+    public void setRoles(Set<Role> roles) {
+        this.roles = roles;
+    }
+
+}
+
+
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/Gender.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/Gender.java
deleted file mode 100644
index 1944b1a..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/Gender.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.party;
-
-/**
- * Created by IntelliJ IDEA. User: Les Date: Sep 4, 2006 Time: 7:29:33 PM To change this template
- * use File | Settings | File Templates.
- */
-public enum Gender {
-
-    MALE("M", "gender.male"),
-
-    FEMALE("F", "gender.female");
-
-    private String initial;
-    private String i18nCode = null;
-
-    private Gender(String initial, String i18nCode) {
-        this.initial = initial;
-        this.i18nCode = i18nCode;
-    }
-
-    public String getName() {
-        return name();
-    }
-
-    public String getInitial() {
-        return initial;
-    }
-
-    public String getI18nCode() {
-        return i18nCode;
-    }
-
-    public String toInitial() {
-        return getInitial();
-    }
-
-    public static Gender fromInitial(String initial) {
-        for (Gender gender : values()) {
-            if (gender.getInitial().equals(initial)) {
-                return gender;
-            }
-        }
-
-        throw new IllegalArgumentException("Argument not understood.  Need valid initial that matches those " +
-                "defined in the " + Gender.class.getName() + " enum");
-    }
-
-}
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/Person.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/Person.java
deleted file mode 100644
index 73eba0e..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/Person.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.party;
-
-import org.jsecurity.samples.sprhib.entity.Entity;
-
-import java.text.DateFormat;
-import java.util.Date;
-
-/**
- * @author Les Hazlewood
- */
-public class Person extends Entity {
-
-    private Gender gender;
-    private String nameSalutation;
-    private String givenName;
-    private String middleNames;
-    private String surname;
-    private String nameSuffix;
-    private Date dateOfBirth;
-    private String title;
-
-    public Person() {
-    }
-
-    public Person(String givenName, String surname) {
-        setGivenName(givenName);
-        setSurname(surname);
-    }
-
-    public Person(String givenName, String middleNames, String surname) {
-        setGivenName(givenName);
-        setMiddleNames(middleNames);
-        setSurname(surname);
-    }
-
-
-    public Gender getGender() {
-        return gender;
-    }
-
-    public void setGender(Gender gender) {
-        this.gender = gender;
-    }
-
-    public String getNameSalutation() {
-        return nameSalutation;
-    }
-
-    public void setNameSalutation(String nameSalutation) {
-        this.nameSalutation = nameSalutation;
-    }
-
-    public String getGivenName() {
-        return givenName;
-    }
-
-    public void setGivenName(String givenName) {
-        this.givenName = givenName;
-    }
-
-    public String getMiddleNames() {
-        return middleNames;
-    }
-
-    public void setMiddleNames(String middleNames) {
-        this.middleNames = middleNames;
-    }
-
-    public String getSurname() {
-        return surname;
-    }
-
-    public void setSurname(String surname) {
-        this.surname = surname;
-    }
-
-    public String getNameSuffix() {
-        return nameSuffix;
-    }
-
-    public void setNameSuffix(String nameSuffix) {
-        this.nameSuffix = nameSuffix;
-    }
-
-    public Date getDateOfBirth() {
-        return dateOfBirth;
-    }
-
-    public void setDateOfBirth(Date dateOfBirth) {
-        this.dateOfBirth = dateOfBirth;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    /**
-     * Returns this person's givenName and surname (i.e. first and last)
-     *
-     * @return this person's givenName and surname (i.e. first and last)
-     */
-    public String getSimpleName() {
-        return getSimpleName(true);
-    }
-
-    public String getSimpleName(boolean givenNameFirst) {
-        StringBuffer sb = new StringBuffer();
-
-        String first;
-        String last;
-
-        if (givenNameFirst) {
-            first = getGivenName();
-            last = getSurname();
-        } else {
-            first = getSurname();
-            last = getGivenName();
-        }
-
-        if (first != null) {
-            sb.append(first);
-        }
-
-        if (last != null) {
-            if (first != null) {
-                sb.append(" ");
-            }
-            sb.append(last);
-        }
-
-        return sb.toString();
-    }
-
-    /**
-     * Returns this person's full name, with all name components (salutation, givenName, ..., etc).
-     *
-     * @return this person's full name, with all name components (salutation, givenName, ..., etc).
-     */
-    public String getFullName() {
-        StringBuffer sb = new StringBuffer();
-        if (getNameSalutation() != null) {
-            sb.append(getNameSalutation());
-        }
-        if (getGivenName() != null) {
-            sb.append(" ").append(getGivenName());
-        }
-        if (getMiddleNames() != null) {
-            sb.append(" ").append(getMiddleNames());
-        }
-        if (getSurname() != null) {
-            sb.append(" ").append(getSurname());
-        }
-        if (getNameSuffix() != null) {
-            sb.append(" ").append(getNameSuffix());
-        }
-        return sb.toString().trim();
-    }
-
-    public StringBuffer toStringBuffer() {
-        StringBuffer sb = super.toStringBuffer();
-        sb.append(",gender=").append(getGender());
-        sb.append(",name=").append(getFullName());
-        Date dob = getDateOfBirth();
-        if (dob != null) {
-            DateFormat df = DateFormat.getInstance();
-            sb.append(",dateOfBirth=[").append(df.format(dob.getTime())).append("]");
-        }
-        sb.append(",title=").append(getTitle());
-        return sb;
-    }
-
-    public boolean onEquals(Entity e) {
-
-        if (e instanceof Person) {
-            Person p = (Person) e;
-            return (givenName == null ? p.getGivenName() == null : givenName.equals(p.getGivenName())) &&
-                    (surname == null ? p.getSurname() == null : surname.equals(p.getSurname())) &&
-                    (middleNames == null ? p.getMiddleNames() == null : middleNames.equals(p.getMiddleNames())) &&
-                    (dateOfBirth == null ? p.getDateOfBirth() == null : dateOfBirth.equals(p.getDateOfBirth())) &&
-                    (gender == null ? p.getGender() == null : gender.equals(p.getGender())) &&
-                    (nameSalutation == null ? p.getNameSalutation() == null : nameSalutation.equals(p.getNameSalutation())) &&
-                    (nameSuffix == null ? p.getNameSuffix() == null : nameSuffix.equals(p.getNameSuffix())) &&
-                    (title == null ? p.getTitle() == null : title.equals(p.getTitle()));
-        }
-
-        return false;
-    }
-
-    public int hashCode() {
-        int result = gender != null ? gender.hashCode() : 0;
-        result = 31 * result + (nameSalutation != null ? nameSalutation.hashCode() : 0);
-        result = 31 * result + (givenName != null ? givenName.hashCode() : 0);
-        result = 31 * result + (middleNames != null ? middleNames.hashCode() : 0);
-        result = 31 * result + (surname != null ? surname.hashCode() : 0);
-        result = 31 * result + (nameSuffix != null ? nameSuffix.hashCode() : 0);
-        result = 31 * result + (dateOfBirth != null ? dateOfBirth.hashCode() : 0);
-        result = 31 * result + (title != null ? title.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
-    public Object clone() {
-        Person clone = (Person) super.clone();
-        clone.setGender(getGender());
-        clone.setNameSalutation(getNameSalutation());
-        clone.setGivenName(getGivenName());
-        clone.setMiddleNames(getMiddleNames());
-        clone.setSurname(getSurname());
-        clone.setNameSuffix(getNameSuffix());
-        Date dob = getDateOfBirth();
-        if (dob != null) {
-            clone.setDateOfBirth((Date) dob.clone());
-        }
-        clone.setTitle(getTitle());
-        return clone;
-    }
-
-}
-
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/User.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/User.java
deleted file mode 100644
index 59932fb..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/User.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.party;
-
-import org.jsecurity.authc.Account;
-import org.jsecurity.authz.Permission;
-import org.jsecurity.samples.sprhib.security.Role;
-import org.jsecurity.subject.PrincipalCollection;
-import org.jsecurity.subject.SimplePrincipalCollection;
-
-import java.text.DateFormat;
-import java.util.*;
-import java.util.regex.Pattern;
-
-/**
- * Simple class that represents any User domain entity in any application.  It extends the {@link Person Person} class
- * to show a non-trivial class hierarchy, since such hierarchies exist in most Hibernate applications in one form
- * or another.  Naturally you could ignore the parent class in your application, but it does represent a clean
- * OO way of modeling things.
- *
- * <p>This class implements the {@link org.jsecurity.authc.Account} interface for dead-simple integration
- * with JSecurity - this allows you to use your User objects directly inside of
- * {@link org.jsecurity.realm.Realm Realm} implementations, significantly reducing the implementation effort.</p>
- *
- * <p>Because this class performs its own Realm and Permission checks, and these can happen frequently enough in a
- * production application, it is highly recommended that the internal User {@link #getUserRoles} collection be cached
- * in a 2nd-level cache when using JPA and/or Hibernate.  The hibernate xml configuration for this sample application
- * does in fact do this for your reference (see User.hbm.xml - the 'roles' declaration).</p>
- *
- * <p>If you ever decide not to use JSecurity, the only domain change would be to simply remove the
- * <code>Account</code> interface declaration</p>
- *
- * @author Les Hazlewood
- */
-public class User extends Person implements Account {
-
-    /**
-     * Requires 6 or more alphanumeric and/or punctuation characters.
-     */
-    public static final Pattern VALID_PASSWORD_PATTERN = Pattern.compile("[\\p{Alnum}\\p{Punct}]{6,255}");
-
-    public static final Pattern VALID_USERNAME_PATTERN = Pattern.compile("[\\p{Alnum}_-]{1,255}");
-
-    public static final String ROOT_USER_USERNAME = "root";
-
-    private String username;
-    private String password;
-    private String passwordResetKey; //UUID generated when they ask to reset the password
-    private Date passwordResetKeyTimestamp; //when they asked to reset the password
-    private Date lastLoginTimestamp; //can be null if never logged in
-    private Date lockTimestamp; //date the account was locked, null means unlocked (default behavior)
-    private boolean sessionTimeoutEnabled = true; //per-user session configuration
-
-    private Set<Role> roles;
-
-    public User() {
-    }
-
-
-    /**
-     * Returns the username associated with this user account;
-     *
-     * @return the username associated with this user account;
-     */
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    /**
-     * Returns the password for this user.
-     *
-     * @return this user's password
-     */
-    public String getPassword() {
-        return password;
-    }
-
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    /**
-     * If the user forgets their password, this key is set first.  If they then request to reset their password, they
-     * must submit this key for the reset request to be valid.  Otherwise, the password reset is timed out after a
-     * certain amount of time after {@link #getPasswordResetKeyTimestamp() passwordResetKeyTimestamp}
-     *
-     * @return
-     */
-    public String getPasswordResetKey() {
-        return passwordResetKey;
-    }
-
-    public void setPasswordResetKey(String passwordResetKey) {
-        this.passwordResetKey = passwordResetKey;
-    }
-
-    public Date getPasswordResetKeyTimestamp() {
-        return passwordResetKeyTimestamp;
-    }
-
-    public void setPasswordResetKeyTimestamp(Date passwordResetKeyTimestamp) {
-        this.passwordResetKeyTimestamp = passwordResetKeyTimestamp;
-    }
-
-    /**
-     * Returns the timestamp this User last logged in successfully to the application, or
-     * <tt>null</tt> if the user has never logged in.
-     *
-     * @return the timestamp this User last logged in successfully to the application, or
-     *         <tt>null</tt> if the user has never logged in.
-     */
-    public Date getLastLoginTimestamp() {
-        return lastLoginTimestamp;
-    }
-
-    /**
-     * Sets the timestamp this User last logged in successfully to the application.
-     *
-     * @param lastLoginTimestamp the timestamp this User last logged in successfully to the
-     *                           application.
-     */
-    public void setLastLoginTimestamp(Date lastLoginTimestamp) {
-        this.lastLoginTimestamp = lastLoginTimestamp;
-    }
-
-    /**
-     * Returns the time when this account was locked, either due to too many login attempts, an
-     * explicit lock-out by an administrator, or because of some other security reason. <p>This
-     * method returns <tt>null</tt> if the account is not locked and is considered to be in good
-     * standing</p>
-     *
-     * @return the time when this account was locked, or <tt>null</tt> if this account is not locked
-     *         and is considered to be in good standing.
-     */
-    public Date getLockTimestamp() {
-        return lockTimestamp;
-    }
-
-    public void setLockTimestamp(Date lockTimestamp) {
-        this.lockTimestamp = lockTimestamp;
-    }
-
-    /**
-     * Returns whether or not this particular user account can expire due to inactivity. <p>Defaults
-     * to <tt>true</tt> as almost all user accounts should expire due to inactivity.
-     *
-     * @return <tt>true</tt> if this user's sessions can timeout due to inactivity, <tt>false</tt>
-     *         otherwise.
-     */
-    public boolean isSessionTimeoutEnabled() {
-        return sessionTimeoutEnabled;
-    }
-
-    public void setSessionTimeoutEnabled(boolean sessionTimeoutEnabled) {
-        this.sessionTimeoutEnabled = sessionTimeoutEnabled;
-    }
-
-    /**
-     * Returns whether or not this user account is locked, thereby preventing further log-ins.
-     *
-     * @return <tt>true</tt> if this user account is locked and will not be allowed to log-in,
-     *         <tt>false</tt>
-     */
-    public boolean isLocked() {
-        return getLockTimestamp() != null;
-    }
-
-    /**
-     * Convenience method for updating the state to locked.
-     *
-     * @param locked whether or not this user account will be locked.
-     * @see #getLockTimestamp()
-     */
-    public void setLocked(boolean locked) {
-        if (locked) {
-            if (getLockTimestamp() == null) {
-                setLockTimestamp(new Date());
-            }
-        } else {
-            setLockTimestamp(null);
-        }
-    }
-
-    public Set<Role> getUserRoles() {
-        return roles;
-    }
-
-    public void setUserRoles(Set<Role> roles) {
-        this.roles = roles;
-    }
-
-    public Role getRole(String name) {
-        Collection<Role> roles = getUserRoles();
-        if (roles != null && !roles.isEmpty()) {
-            for (Role role : roles) {
-                if (role.getName().equals(name)) {
-                    return role;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Adds a Role to this user's collection of {@link #getRoles() roles}.
-     *
-     * <p>If the existing roles collection is <tt>null</tt>, a new collection will be created and
-     * assigned to this User and then the Role will be added.
-     *
-     * @param r the Role to add/associate with this User
-     */
-    public void add(Role r) {
-        Set<Role> roles = getUserRoles();
-        if (roles == null) {
-            roles = new LinkedHashSet<Role>();
-            setUserRoles(roles);
-        }
-        roles.add(r);
-    }
-
-    public boolean removeRole(Role r) {
-        Set<Role> roles = getUserRoles();
-        return roles != null && roles.remove(r);
-    }
-
-    protected String getPrivateRoleName(PrincipalCollection principals) {
-        return getClass().getName() + "_PRIVATE_ROLE_" + PrincipalCollection.class.getName();
-    }
-
-    protected Role createPrivateRole(PrincipalCollection principals) {
-        String privateRoleName = getPrivateRoleName(principals);
-        return new Role(privateRoleName, this);
-    }
-
-    public Set<Permission> getPermissions() {
-        Set<Permission> permissions = new HashSet<Permission>();
-        for (Role role : roles) {
-            permissions.addAll(role.getPermissions());
-        }
-        return permissions;
-    }
-
-    public Set<String> getRolenames() {
-        Set<String> rolenames = new HashSet<String>();
-        for (Role role : roles) {
-            rolenames.add(role.getName());
-        }
-        return rolenames;
-    }
-
-    public void addRole(String roleName) {
-        Role existing = getRole(roleName);
-        if (existing == null) {
-            Role role = new Role(roleName);
-            add(role);
-        }
-    }
-
-    public void addRoles(Set<String> roleNames) {
-        if (roleNames != null && !roleNames.isEmpty()) {
-            for (String name : roleNames) {
-                addRole(name);
-            }
-        }
-    }
-
-    public void addAll(Collection<Role> roles) {
-        if (roles != null && !roles.isEmpty()) {
-            Set<Role> existingRoles = getUserRoles();
-            if (existingRoles == null) {
-                existingRoles = new LinkedHashSet<Role>(roles.size());
-                setUserRoles(existingRoles);
-            }
-            existingRoles.addAll(roles);
-        }
-    }
-
-
-    public static boolean isValidPassword(String password) {
-        return password != null && VALID_PASSWORD_PATTERN.matcher(password).matches();
-    }
-
-    public static boolean isValidUsername(String username) {
-        return username != null && VALID_USERNAME_PATTERN.matcher(username).matches();
-    }
-
-
-    public StringBuffer toStringBuffer() {
-        StringBuffer sb = super.toStringBuffer();
-        sb.append(",username=").append(getUsername());
-        sb.append(",password=<protected>");
-        DateFormat df = DateFormat.getInstance();
-        Date ts = getLastLoginTimestamp();
-        if (ts != null) {
-            sb.append(",lastLoginTimestamp=").append(df.format(ts));
-        }
-        ts = getLockTimestamp();
-        if (ts != null) {
-            sb.append(",lockTimestamp=").append(df.format(ts));
-        }
-        sb.append(",sessionTimeoutEnabled=").append(isSessionTimeoutEnabled());
-
-        return sb;
-    }
-
-    public boolean onEquals(Object o) {
-        if (o instanceof User) {
-            User u = (User) o;
-            return getUsername().equals(u.getUsername());
-        }
-
-        return false;
-    }
-
-    public int hashCode() {
-        return getUsername().hashCode();
-    }
-
-    @Override
-    @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
-    public Object clone() {
-        User clone = (User) super.clone();
-        clone.setUsername(getUsername());
-        clone.setPassword(getPassword());
-        clone.setPasswordResetKey(getPasswordResetKey());
-        clone.setPasswordResetKeyTimestamp(getPasswordResetKeyTimestamp());
-        clone.setLastLoginTimestamp(getLastLoginTimestamp());
-        clone.setLockTimestamp(getLockTimestamp());
-        clone.setSessionTimeoutEnabled(isSessionTimeoutEnabled());
-        return clone;
-    }
-
-    public static void main(String[] args) {
-        String username = "s-ls";
-        if (!isValidUsername(username)) {
-            System.out.println("Not a valid username!");
-        } else {
-            System.out.println("Valid username.");
-        }
-    }
-
-    /* ===========
-       JSecurity Account implementations below here.
-       =========== */
-    public PrincipalCollection getPrincipals() {
-        //The realm name must match the name of the configured realm.
-        return new SimplePrincipalCollection(getId(), "DefaultRealm");
-    }
-
-    public Object getCredentials() {
-        return getPassword();
-    }
-
-    public boolean isCredentialsExpired() {
-        //if applications wanted to expire passwords after a certain amount of time, this method would calculate
-        //true or false based on the current time and a passwordLastUpdateTimestamp;
-
-        //this sample app doesn't use this feature, so just return false always:
-        return false;
-    }
-
-    public Collection<String> getRoles() {
-        return getRolenames();
-    }
-
-    public Collection<String> getStringPermissions() {
-        // This model uses object permissions, so this method isn't implemented
-        return null;
-    }
-
-    public Collection<Permission> getObjectPermissions() {
-        Set<Permission> permissions = new HashSet<Permission>();
-        for (Role role : getUserRoles()) {
-            permissions.addAll(role.getPermissions());
-        }
-        return permissions;
-    }
-}
-
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/eis/HibernateUserDAO.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/eis/HibernateUserDAO.java
deleted file mode 100644
index 373bebc..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/party/eis/HibernateUserDAO.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.party.eis;
-
-import org.jsecurity.samples.sprhib.eis.hibernate.PrimaryClassHibernateDAO;
-import org.jsecurity.samples.sprhib.party.User;
-
-/**
- * @author Les Hazlewood
- */
-public class HibernateUserDAO extends PrimaryClassHibernateDAO implements UserDAO {
-
-    public HibernateUserDAO() {
-        setPrimaryClass(User.class);
-    }
-
-    public User getUser(Long userId) {
-        return (User) read(userId);
-    }
-
-    public User findUser(String username) {
-        if (username == null) {
-            String msg = "Username cannot be null.";
-            throw new IllegalArgumentException(msg);
-        }
-        String query = "from User u where u.username = ?";
-        return (User) findSingle(query, username);
-    }
-
-    /*public User findUserByEmail( String email ) {
-        if ( email == null ) {
-            String msg = "Email argument cannot be null.";
-            throw new IllegalArgumentException( msg );
-        }
-        String query = "from User u where u.emailAddresses.text = ?";
-        return (User)findSingle( query, email );
-    }*/
-
-}
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/Role.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/Role.java
deleted file mode 100644
index 3fd725f..0000000
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/Role.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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.jsecurity.samples.sprhib.security;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.jsecurity.authz.Permission;
-import org.jsecurity.samples.sprhib.entity.Entity;
-import org.jsecurity.samples.sprhib.party.User;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Created on: Sep 16, 2005 4:00:20 PM
- *
- * @author Les Hazlewood
- */
-public class Role extends Entity {
-
-    private static final Log log = LogFactory.getLog(Role.class);
-
-    public static final String ROOT_ROLE_NAME = "root";
-    public static final String PRIVATE_ROLE_NAME = "private";
-
-    private String name;
-
-    private String description;
-
-    private User owner;
-
-    private boolean isPrivate = false;
-
-    private Set<Permission> permissions;
-
-    public Role() {
-    }
-
-    public Role(String name) {
-        this.name = name;
-    }
-
-    public Role(String name, User owner) {
-        this.name = name;
-        this.owner = owner;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public boolean isPrivate() {
-        return isPrivate;
-    }
-
-    public void setPrivate(boolean isPrivate) {
-        this.isPrivate = isPrivate;
-    }
-
-    public User getOwner() {
-        return owner;
-    }
-
-    public void setOwner(User owner) {
-        this.owner = owner;
-    }
-
-    public Set<Permission> getPermissions() {
-        return permissions;
-    }
-
-    public void setPermissions(Set<Permission> permissions) {
-        this.permissions = permissions;
-    }
-
-    /**
-     * Adds a Permission to this party's collection of
-     * {@link #getPermissions() permissions}.
-     *
-     * <p>If the existing permissions collection is <tt>null</tt>,
-     * a new collection will be created and assigned to this role and then the permission will
-     * be added.
-     *
-     * <p>If the specified permission already exists in this role's collection, it will not
-     * be added again.
-     *
-     * @param p the Permission to add/associate with this Role
-     */
-    public void add(Permission p) {
-        Set<Permission> perms = getPermissions();
-        if (perms == null) {
-            perms = new HashSet<Permission>();
-            setPermissions(perms);
-        }
-        perms.add(p);
-    }
-
-    public boolean remove(Permission p) {
-        Set<Permission> perms = getPermissions();
-        return perms != null && perms.remove(p);
-    }
-
-    public boolean isPermitted(Permission p) {
-        Set<Permission> perms = getPermissions();
-        if (perms != null && !perms.isEmpty()) {
-            for (Permission perm : perms) {
-                if (perm.implies(p)) {
-                    if (log.isTraceEnabled()) {
-                        String msg = "saved permission implies permission argument.  Role [" +
-                                getName() + "] has permission";
-                        log.trace(msg);
-                    }
-                    return true;
-                }
-            }
-        }
-
-        if (log.isTraceEnabled()) {
-            log.trace("No saved permissions implies the permission argument.  Role [" +
-                    getName() + "] doesn't have the specified permission");
-        }
-
-        return false;
-    }
-
-    public boolean onEquals(Entity e) {
-        if (e instanceof Role) {
-            Role r = (Role) e;
-            return getName().equals(r.getName()) &&
-                    (owner != null ? owner.equals(r.getOwner()) : r.getOwner() == null);
-        }
-
-        return false;
-    }
-
-    public int hashCode() {
-        int result = name.hashCode();
-        result = 29 * result + (owner != null ? owner.hashCode() : 0);
-        return result;
-    }
-
-    public StringBuffer toStringBuffer() {
-        StringBuffer sb = super.toStringBuffer();
-        sb.append(",name=").append(getName());
-        sb.append(",description=[").append(getDescription()).append("]");
-        sb.append(",permissions={").append("<lazy property omitted>").append("}");
-        return sb;
-    }
-
-    /**
-     * Returns a shallow copy (i.e. the owner and Permission instances in the permissions
-     * collection copied into a new list instead of being cloned themselves).  This should be fine since permission
-     * objects are immutable.
-     */
-    @Override
-    @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
-    public Object clone() {
-        Role clone = (Role) super.clone();
-        clone.setName(getName());
-        clone.setDescription(getDescription());
-        clone.setOwner(getOwner());
-        Set<Permission> perms = getPermissions();
-        if (perms != null && !perms.isEmpty()) {
-            Set<Permission> permClones = new HashSet<Permission>(perms.size());
-            for (Permission p : perms) {
-                permClones.add(p);
-            }
-            clone.setPermissions(permClones);
-        }
-
-        return clone;
-    }
-
-    public void clearPermissions() {
-        Set<Permission> perms = getPermissions();
-        if (perms != null && !perms.isEmpty()) {
-            permissions.clear();
-        }
-    }
-
-}
-
-
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/DefaultRealm.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/SampleRealm.java
similarity index 63%
rename from samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/DefaultRealm.java
rename to samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/SampleRealm.java
index 62e6526..607bea6 100644
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/DefaultRealm.java
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/security/SampleRealm.java
@@ -18,15 +18,17 @@
  */
 package org.jsecurity.samples.sprhib.security;
 
-import org.jsecurity.authc.AuthenticationException;
-import org.jsecurity.authc.AuthenticationInfo;
-import org.jsecurity.authc.AuthenticationToken;
-import org.jsecurity.authc.UsernamePasswordToken;
+import org.jsecurity.authc.*;
+import org.jsecurity.authc.credential.Sha256CredentialsMatcher;
 import org.jsecurity.authz.AuthorizationInfo;
+import org.jsecurity.authz.SimpleAuthorizationInfo;
 import org.jsecurity.realm.AuthorizingRealm;
-import org.jsecurity.samples.sprhib.party.eis.UserDAO;
+import org.jsecurity.samples.sprhib.dao.UserDAO;
+import org.jsecurity.samples.sprhib.model.Role;
+import org.jsecurity.samples.sprhib.model.User;
 import org.jsecurity.subject.PrincipalCollection;
-import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
 
 /**
  * The Spring/Hibernate sample application's one and only configured JSecurity Realm.
@@ -35,39 +37,47 @@
  * in the implementation and named it a 'HibernateRealm' or something similar.</p>
  *
  * <p>But we've decided to make the calls to the database using a UserDAO, since a DAO would be used in other areas
- * of a 'real' application in addition to here. We felt it better to use that same DAO to show code re-use.
- * That is, in a real app, there is no need to duplicate Hibernate calls in the Realm implementation if you've already
- * got a User DAO (as most apps would).  So, we just use that UserDAO here.</p>
- *
- * @author Les Hazlewood
+ * of a 'real' application in addition to here. We felt it better to use that same DAO to show code re-use.</p>
  */
-public class DefaultRealm extends AuthorizingRealm implements InitializingBean {
+@Component
+public class SampleRealm extends AuthorizingRealm {
 
     protected UserDAO userDAO = null;
 
-    public DefaultRealm() {
+    public SampleRealm() {
         setName("DefaultRealm"); //This name must match the name in the User class's getPrincipals() method
+        setCredentialsMatcher(new Sha256CredentialsMatcher());
     }
 
+    @Autowired
     public void setUserDAO(UserDAO userDAO) {
         this.userDAO = userDAO;
     }
 
-    public void afterPropertiesSet() throws Exception {
-        if (this.userDAO == null) {
-            throw new IllegalStateException("UserDAO property was not injected.  Please check your Spring config.");
-        }
-    }
-
     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
         UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
-        return userDAO.findUser(token.getUsername());
+        User user = userDAO.findUser(token.getUsername());
+        if( user != null ) {
+            return new SimpleAuthenticationInfo(user.getId(), user.getPassword(), getName());
+        } else {
+            return null;
+        }
     }
 
 
     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
         Long userId = (Long) principals.fromRealm(getName()).iterator().next();
-        return userDAO.getUser(userId);
+        User user = userDAO.getUser(userId);
+        if( user != null ) {
+            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
+            for( Role role : user.getRoles() ) {
+                info.addRole(role.getName());
+                info.addStringPermissions( role.getPermissions() );
+            }
+            return info;
+        } else {
+            return null;
+        }
     }
 
 }
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/service/DefaultUserService.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/service/DefaultUserService.java
new file mode 100644
index 0000000..1485f9c
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/service/DefaultUserService.java
@@ -0,0 +1,79 @@
+/*
+ * 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.jsecurity.samples.sprhib.service;
+
+import org.jsecurity.SecurityUtils;
+import org.jsecurity.crypto.hash.Sha256Hash;
+import org.jsecurity.samples.sprhib.dao.UserDAO;
+import org.jsecurity.samples.sprhib.model.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * Default implementation of the {@link UserService} interface.  This service implements
+ * operations related to User data.
+ */
+@Transactional
+@Service("userService")
+public class DefaultUserService implements UserService {
+
+    private UserDAO userDAO;
+
+    @Autowired
+    public void setUserDAO(UserDAO userDAO) {
+        this.userDAO = userDAO;
+    }
+
+    public User getCurrentUser() {
+        final Long currentUserId = (Long) SecurityUtils.getSubject().getPrincipal();
+        if( currentUserId != null ) {
+            return getUser(currentUserId);
+        } else {
+            return null;
+        }
+    }
+
+    public void createUser(String username, String email, String password) {
+        User user = new User();
+        user.setUsername(username);
+        user.setEmail(email);
+        user.setPassword( new Sha256Hash(password).toHex() );
+        userDAO.createUser( user );
+    }
+
+    public List<User> getAllUsers() {
+        return userDAO.getAllUsers();
+    }
+
+    public User getUser(Long userId) {
+        return userDAO.getUser(userId);
+    }
+
+    public void deleteUser(Long userId) {
+        userDAO.deleteUser( userId );
+    }
+
+    public void updateUser(User user) {
+        userDAO.updateUser( user );
+    }
+
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/entity/Identifiable.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/service/UserService.java
similarity index 64%
rename from samples/spring-hibernate/src/org/jsecurity/samples/sprhib/entity/Identifiable.java
rename to samples/spring-hibernate/src/org/jsecurity/samples/sprhib/service/UserService.java
index 71dc685..522393c 100644
--- a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/entity/Identifiable.java
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/service/UserService.java
@@ -16,18 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.jsecurity.samples.sprhib.entity;
+package org.jsecurity.samples.sprhib.service;
+
+import org.jsecurity.samples.sprhib.model.User;
+
+import java.util.List;
 
 /**
- * <p>This interface was borrowed from the <a href="http://code.google.com/p/pojodm">PojoDM Project</a>'s
- * <a href="http://code.google.com/p/pojodm/source/browse/trunk/src/org/pojodm/entity/Identifiable.java">Identifiable.java</a>
- * for a quick kickstart.</p>
- *
- * @author Les Hazlewood
- * @since Apr 5, 2008 2:46:56 PM
+ * A service interface for accessing and modifying user data in the system.
  */
-public interface Identifiable {
+public interface UserService {
 
-    Long getId();
+    User getCurrentUser();
 
+    void createUser(String username, String email, String password);
+
+    List<User> getAllUsers();
+
+    User getUser(Long userId);
+
+    void deleteUser(Long userId);
+
+    void updateUser(User user);
 }
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/CurrentUserInterceptor.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/CurrentUserInterceptor.java
new file mode 100644
index 0000000..bbe83b6
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/CurrentUserInterceptor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.samples.sprhib.model.User;
+import org.jsecurity.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A Spring MVC interceptor that adds the currentUser into the request as a request attribute
+ * before the JSP is rendered.  This operation is assumed to be fast because the User should be
+ * cached in the Hibernate second-level cache.
+ */
+@Component
+public class CurrentUserInterceptor extends HandlerInterceptorAdapter {
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
+        // Add the current user into the request
+        User currentUser = userService.getCurrentUser();
+        if( currentUser != null ) {
+            httpServletRequest.setAttribute( "currentUser", currentUser );
+        }
+    }
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/EditUserCommand.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/EditUserCommand.java
new file mode 100644
index 0000000..92e65d6
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/EditUserCommand.java
@@ -0,0 +1,76 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.crypto.hash.Sha256Hash;
+import org.jsecurity.samples.sprhib.model.User;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Command binding object for editing a user.
+ */
+public class EditUserCommand {
+
+    private Long userId;
+    private String username;
+    private String email;
+    private String password;
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public void updateUser(User user) {
+        Assert.isTrue( userId.equals( user.getId() ), "User ID of command must match the user being updated." );
+        user.setUsername( getUsername() );
+        user.setEmail( getEmail() );
+        if( StringUtils.hasText(getPassword()) ) {
+            user.setPassword( new Sha256Hash(getPassword()).toHex() );
+        }
+    }
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/EditUserValidator.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/EditUserValidator.java
new file mode 100644
index 0000000..5f78cd0
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/EditUserValidator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.util.StringUtils;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+import java.util.regex.Pattern;
+
+/**
+ * Validator when editing a user.
+ */
+public class EditUserValidator implements Validator {
+
+    private static final String SIMPLE_EMAIL_REGEX = "[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}";
+
+    public boolean supports(Class aClass) {
+        return EditUserCommand.class.isAssignableFrom(aClass);
+    }
+
+    public void validate(Object o, Errors errors) {
+        EditUserCommand command = (EditUserCommand)o;
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "error.username.empty", "Please specify a username.");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "error.email.empty", "Please specify an email address.");
+        if( StringUtils.hasText( command.getEmail() ) && !Pattern.matches( SIMPLE_EMAIL_REGEX, command.getEmail().toUpperCase() ) ) {
+            errors.rejectValue( "email", "error.email.invalid", "Please enter a valid email address." );
+        }
+    }
+
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/HomeController.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/HomeController.java
new file mode 100644
index 0000000..4630299
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/HomeController.java
@@ -0,0 +1,45 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * Web controller used when loading the home page.
+ */
+@Controller
+public class HomeController {
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @RequestMapping("/home")
+    public void viewHome(Model model) {
+        model.addAttribute( "users", userService.getAllUsers() );
+    }
+
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/LoginCommand.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/LoginCommand.java
new file mode 100644
index 0000000..8ff353d
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/LoginCommand.java
@@ -0,0 +1,55 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+/**
+ * Command binding object for logging in.
+ */
+public class LoginCommand {
+
+    private String username;
+
+    private String password;
+
+    private boolean rememberMe;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public boolean isRememberMe() {
+        return rememberMe;
+    }
+
+    public void setRememberMe(boolean rememberMe) {
+        this.rememberMe = rememberMe;
+    }
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/LoginValidator.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/LoginValidator.java
new file mode 100644
index 0000000..a116a38
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/LoginValidator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+/**
+ * Validator for login.
+ */
+public class LoginValidator implements Validator {
+    public boolean supports(Class aClass) {
+        return LoginCommand.class.isAssignableFrom(aClass);
+    }
+
+    public void validate(Object o, Errors errors) {
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "error.username.empty", "Please specify a username.");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "error.password.empty", "Please specify a password.");
+    }
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/ManageUsersController.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/ManageUsersController.java
new file mode 100644
index 0000000..f43b0e0
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/ManageUsersController.java
@@ -0,0 +1,91 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.authz.annotation.RequiresPermissions;
+import org.jsecurity.samples.sprhib.model.User;
+import org.jsecurity.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.util.Assert;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * Web MVC controller that handles operations related to managing users, such as editing them and deleting them. 
+ */
+@Controller
+public class ManageUsersController {
+
+    private EditUserValidator editUserValidator = new EditUserValidator();
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @RequestMapping("/manageUsers")
+    @RequiresPermissions("user:manage")
+    public void manageUsers(Model model) {
+        model.addAttribute("users", userService.getAllUsers());
+    }
+
+    @RequestMapping(value="/editUser",method= RequestMethod.GET)
+    @RequiresPermissions("user:edit")
+    public String showEditUserForm(Model model, @RequestParam Long userId, @ModelAttribute EditUserCommand command) {
+
+        User user = userService.getUser( userId );
+        command.setUserId(userId);
+        command.setUsername(user.getUsername());
+        command.setEmail(user.getEmail());
+        return "editUser";
+    }
+
+    @RequestMapping(value="/editUser",method= RequestMethod.POST)
+    @RequiresPermissions("user:edit")
+    public String editUser(Model model, @RequestParam Long userId, @ModelAttribute EditUserCommand command, BindingResult errors) {
+        editUserValidator.validate( command, errors );
+
+        if( errors.hasErrors() ) {
+            return "editUser";
+        }
+
+        User user = userService.getUser( userId );
+        command.updateUser( user );
+
+        userService.updateUser( user );
+
+        return "redirect:/s/manageUsers";
+    }
+
+    @RequestMapping("/deleteUser")
+    @RequiresPermissions("user:delete")
+    public String deleteUser(@RequestParam Long userId) {
+        Assert.isTrue( userId != 1, "Cannot delete admin user" );
+        userService.deleteUser( userId );
+        return "redirect:/s/manageUsers";
+    }
+
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SecurityController.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SecurityController.java
new file mode 100644
index 0000000..0ef19d7
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SecurityController.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.SecurityUtils;
+import org.jsecurity.authc.AuthenticationException;
+import org.jsecurity.authc.UsernamePasswordToken;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Web MVC controller that handles security-related web requests, such as login and logout.
+ */
+@Controller
+public class SecurityController {
+
+    private LoginValidator loginValidator = new LoginValidator();
+
+    @RequestMapping(value="/login",method= RequestMethod.GET)
+    public String showLoginForm(Model model, @ModelAttribute LoginCommand command ) {
+        return "login";
+    }
+
+    @RequestMapping(value="/login",method= RequestMethod.POST)
+    public String login(Model model, @ModelAttribute LoginCommand command, BindingResult errors) {
+        loginValidator.validate(command, errors);
+
+        if( errors.hasErrors() ) {
+            return showLoginForm(model, command);
+        }
+
+        UsernamePasswordToken token = new UsernamePasswordToken(command.getUsername(), command.getPassword(), command.isRememberMe());
+        try {
+            SecurityUtils.getSubject().login(token);
+        } catch (AuthenticationException e) {
+            errors.reject( "error.login.generic", "Invalid username or password.  Please try again." );
+        }
+
+        if( errors.hasErrors() ) {
+            return showLoginForm(model, command);
+        } else {
+            return "redirect:/s/home";
+        }
+    }
+
+    @RequestMapping("/logout")
+    public String logout() {
+        SecurityUtils.getSubject().logout();
+        return "redirect:/";
+    }
+
+
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupCommand.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupCommand.java
new file mode 100644
index 0000000..4e31400
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupCommand.java
@@ -0,0 +1,55 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+/**
+ * Command binding object for signing up for a new account. 
+ */
+public class SignupCommand {
+
+    private String username;
+
+    private String email;
+
+    private String password;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupController.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupController.java
new file mode 100644
index 0000000..94d0783
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupController.java
@@ -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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.SecurityUtils;
+import org.jsecurity.authc.UsernamePasswordToken;
+import org.jsecurity.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Web MVC controller that handles signup requests.
+ */
+@Controller
+public class SignupController {
+
+    private SignupValidator signupValidator = new SignupValidator();
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @RequestMapping(value="/signup",method= RequestMethod.GET)
+    public String showSignupForm(Model model, @ModelAttribute SignupCommand command) {
+        return "signup";
+    }
+
+    @RequestMapping(value="/signup",method= RequestMethod.POST)
+    public String showSignupForm(Model model, @ModelAttribute SignupCommand command, BindingResult errors) {
+        signupValidator.validate(command, errors);
+
+        if( errors.hasErrors() ) {
+            return showSignupForm(model, command);
+        }
+
+        // Create the user
+        userService.createUser( command.getUsername(), command.getEmail(), command.getPassword() );
+
+        // Login the newly created user
+        SecurityUtils.getSubject().login(new UsernamePasswordToken(command.getUsername(), command.getPassword()));
+
+        return "redirect:/s/home";
+    }
+
+}
diff --git a/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupValidator.java b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupValidator.java
new file mode 100644
index 0000000..0e7f27f
--- /dev/null
+++ b/samples/spring-hibernate/src/org/jsecurity/samples/sprhib/web/SignupValidator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.jsecurity.samples.sprhib.web;
+
+import org.jsecurity.util.StringUtils;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+import java.util.regex.Pattern;
+
+/**
+ * Validator for the signup form.
+ */
+public class SignupValidator implements Validator {
+
+    private static final String SIMPLE_EMAIL_REGEX = "[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}";
+
+    public boolean supports(Class aClass) {
+        return SignupCommand.class.isAssignableFrom(aClass);
+    }
+
+    public void validate(Object o, Errors errors) {
+        SignupCommand command = (SignupCommand)o;
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "error.username.empty", "Please specify a username.");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "error.email.empty", "Please specify an email address.");
+        if( StringUtils.hasText( command.getEmail() ) && !Pattern.matches( SIMPLE_EMAIL_REGEX, command.getEmail().toUpperCase() ) ) {
+            errors.rejectValue( "email", "error.email.invalid", "Please enter a valid email address." );
+        }
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "error.password.empty", "Please specify a password.");
+    }
+}
diff --git a/samples/spring-hibernate/web/WEB-INF/applicationContext.xml b/samples/spring-hibernate/web/WEB-INF/applicationContext.xml
new file mode 100644
index 0000000..b25e833
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/applicationContext.xml
@@ -0,0 +1,104 @@
+<?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.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xmlns:tx="http://www.springframework.org/schema/tx"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
+       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
+       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+    <!-- Enable annotation configuration -->
+    <context:annotation-config/>
+
+    <!-- Scan sample packages for Spring annotations -->
+    <context:component-scan base-package="org.jsecurity.samples.sprhib.dao"/>
+    <context:component-scan base-package="org.jsecurity.samples.sprhib.security"/>
+    <context:component-scan base-package="org.jsecurity.samples.sprhib.service"/>
+
+    <!-- Spring AOP auto-proxy creation (required to support JSecurity annotations) -->
+    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
+
+
+    <!-- Sample RDBMS data source that would exist in any application.  Sample is just using an in-memory HSQLDB
+         instance.  Change to your application's settings for a real app. -->
+    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
+        <property name="url" value="jdbc:hsqldb:mem:jsecurity-spring-hibernate"/>
+        <property name="username" value="sa"/>
+    </bean>
+
+    <!-- Hibernate SessionFactory -->
+    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
+        <property name="dataSource" ref="dataSource"/>
+        <!-- Because we're using an in-memory database for demo purposes (which is lost every time the app
+             shuts down), we have to ensure that the HSQLDB DDL is run each time the app starts.  The
+             DDL is auto-generated based on the *.hbm.xml mapping definitions below. -->
+        <property name="schemaUpdate" value="true"/>
+        <!-- Scan packages for JPA annotations -->
+        <property name="packagesToScan" value="org.jsecurity.samples.sprhib.model"/>
+        <property name="hibernateProperties">
+            <props>
+                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
+                <prop key="hibernate.jdbc.fetch_size">100</prop>
+                <prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop>
+            </props>
+        </property>
+        <property name="eventListeners">
+            <map>
+                <entry key="merge">
+                    <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
+                </entry>
+            </map>
+        </property>
+    </bean>
+
+    <!-- Transaction support beans -->
+    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
+        <property name="sessionFactory" ref="sessionFactory"/>
+    </bean>
+
+    <tx:annotation-driven/>
+
+
+    <!-- =========================================================
+         JSecurity Components
+         ========================================================= -->
+
+    <!-- JSecurity's main business-tier object for web-enabled applications
+         (use org.jsecurity.mgt.DefaultSecurityManager instead when there is no web environment)-->
+    <bean id="securityManager" class="org.jsecurity.web.DefaultWebSecurityManager">
+        <!-- Single realm app (realm configured next, below).  If you have multiple realms, use the 'realms'
+      property instead. -->
+        <property name="realm" ref="sampleRealm"/>
+        <!-- Uncomment this next property if you want heterogenous session access or clusterable/distributable
+             sessions.  The default value is 'http' which uses the Servlet container's HttpSession as the underlying
+             Session implementation.
+        <property name="sessionMode" value="jsecurity"/> -->
+    </bean>
+
+
+    <!-- Post processor that automatically invokes init() and destroy() methods -->
+    <bean id="lifecycleBeanPostProcessor" class="org.jsecurity.spring.LifecycleBeanPostProcessor"/>
+
+</beans>
diff --git a/samples/spring-hibernate/web/WEB-INF/classes/ehcache.xml b/samples/spring-hibernate/web/WEB-INF/classes/ehcache.xml
new file mode 100644
index 0000000..3009bc8
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/classes/ehcache.xml
@@ -0,0 +1,56 @@
+<!--
+  ~ 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.
+  -->
+<ehcache>
+
+    <diskStore path="java.io.tmpdir/jsecurity-spring-hibernate-ehcache"/>
+
+
+    <defaultCache
+        maxElementsInMemory="1000"
+        eternal="false"
+        timeToLiveSeconds="600"
+        overflowToDisk="true"
+        diskPersistent="false"
+        />
+
+    <!--=================================================================
+        Hibernate Object Caches
+        =================================================================-->
+
+    <cache name="org.jsecurity.samples.sprhib.model.Role"
+        maxElementsInMemory="100"
+        timeToLiveSeconds="0"
+        overflowToDisk="true"/>
+
+    <cache name="org.jsecurity.samples.sprhib.model.Role.permissions"
+        maxElementsInMemory="100"
+        timeToLiveSeconds="0"
+        overflowToDisk="true"/>
+
+    <cache name="org.jsecurity.samples.sprhib.model.User"
+        maxElementsInMemory="1000"
+        timeToLiveSeconds="3600"
+        overflowToDisk="true"/>
+
+    <cache name="org.jsecurity.samples.sprhib.model.User.roles"
+        maxElementsInMemory="1000"
+        timeToLiveSeconds="3600"
+        overflowToDisk="true"/>
+
+</ehcache>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/classes/hibernate.cfg.xml b/samples/spring-hibernate/web/WEB-INF/classes/hibernate.cfg.xml
new file mode 100644
index 0000000..2050a9d
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/classes/hibernate.cfg.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+<!DOCTYPE hibernate-configuration PUBLIC
+  "-//Hibernate/Hibernate Configuration DTD//EN"
+  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
+
+<!-- This file is largely provided for IDE/tool support, and is NOT used at runtime -->
+
+<hibernate-configuration>
+  <session-factory>
+    <!-- DB schema will be updated if needed -->
+    <property name="hbm2ddl.auto">update</property>
+
+    <mapping class="org.jsecurity.samples.sprhib.model.Role"/>
+    <mapping class="org.jsecurity.samples.sprhib.model.User"/>
+
+  </session-factory>
+</hibernate-configuration>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/classes/log4j.properties b/samples/spring-hibernate/web/WEB-INF/classes/log4j.properties
new file mode 100644
index 0000000..54ffb3f
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/classes/log4j.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+log4j.rootLogger=INFO, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# General Apache libraries
+log4j.logger.org.apache=WARN
+
+# Spring
+log4j.logger.org.springframework=WARN
+
+# Hibernate
+log4j.logger.org.hibernate=WARN
+
+# Default JSecurity logging
+log4j.logger.org.jsecurity=TRACE
+
+# Disable verbose logging
+log4j.logger.org.jsecurity.util.ThreadContext=WARN
+log4j.logger.org.jsecurity.cache.ehcache.EhCache=WARN
diff --git a/samples/spring-hibernate/web/WEB-INF/jsp/editUser.jsp b/samples/spring-hibernate/web/WEB-INF/jsp/editUser.jsp
new file mode 100644
index 0000000..c6f7f01
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/jsp/editUser.jsp
@@ -0,0 +1,52 @@
+<%--
+  ~ 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.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<html>
+<head>
+    <title>JSecurity Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+    <div id="box">
+        <div class="title">JSecurity Sample App - Edit User</div>
+
+        <div class="content">
+            <form:form modelAttribute="editUserCommand">
+
+                <form:errors path="*" element="div" cssClass="errors"/>
+                <div><div class="form-label">Username:</div><form:input path="username"/></div>
+                <div><div class="form-label">Email:</div><form:input path="email"/></div>
+                <div><div class="form-label">Password:</div><form:password path="password"/></div>
+                <div><input type="button" onclick="document.location.href='<c:url value="/s/manageUsers"/>'" value="Cancel"/>&nbsp;<input type="submit" value="Save Changes"/></div>
+            </form:form>
+
+            <p>Only edit the password field if you want to change the user's password.  Otherwise leave password blank.</p>
+            
+        </div>
+
+    </div>
+
+    <script type="text/javascript">
+        document.getElementById('username').focus();
+    </script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/jsp/home.jsp b/samples/spring-hibernate/web/WEB-INF/jsp/home.jsp
new file mode 100644
index 0000000..06e99ac
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/jsp/home.jsp
@@ -0,0 +1,55 @@
+<%--
+  ~ 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.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="jsec" uri="http://www.jsecurity.org/tags" %>
+
+<html>
+<head>
+    <title>JSecurity Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+
+<div id="bigbox">
+    <div class="title clearfix"><div style="float: left">JSecurity Sample App - Home</div><div class="info" >Logged in as ${currentUser.username} (<a href="<c:url value="/s/logout"/>">Logout</a>)</div></div>
+
+
+    <div class="content">
+
+        <p>Users in the system:</p>
+        <ul>
+            <c:forEach var="user" items="${users}">
+                <li>${user.username} - ${user.email}</li>
+            </c:forEach>
+        </ul>
+
+        <p>
+        <jsec:hasPermission name="user:manage">
+            Since you are logged in as the admin user, you can <a href="<c:url value="/s/manageUsers"/>">manage site users</a>.
+        </jsec:hasPermission>
+        <jsec:lacksPermission name="user:manage">
+            Since you are not logged in as the admin user, you can't manage site users.
+        </jsec:lacksPermission>
+        </p>
+    </div>
+
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/jsp/login.jsp b/samples/spring-hibernate/web/WEB-INF/jsp/login.jsp
new file mode 100644
index 0000000..95c6766
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/jsp/login.jsp
@@ -0,0 +1,56 @@
+<%--
+  ~ 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.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<html>
+<head>
+    <title>JSecurity Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/> 
+</head>
+<body>
+    <div id="box">
+        <div class="title">JSecurity Sample App - Login</div>
+
+        <div class="content">
+            <form:form modelAttribute="loginCommand">
+
+                <form:errors path="*" element="div" cssClass="errors"/>
+
+                <div><div class="form-label">Username:</div><form:input path="username"/></div>
+                <div><div class="form-label">Password:</div><form:password path="password"/></div>
+                <div><form:checkbox path="rememberMe"/> Remember Me</div>
+                <div><input type="submit" value="Login"/></div>
+            </form:form>
+
+            <div>Don't have an account? <a href="<c:url value="/s/signup"/>">Sign up</a></div>
+        </div>
+    </div>
+
+    <p>
+        Users created through the signup form have the role "user".  You can also log in as admin/admin, which has the
+        "admin" role.
+    </p>
+
+    <script type="text/javascript">
+        document.getElementById('username').focus();
+    </script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/jsp/manageUsers.jsp b/samples/spring-hibernate/web/WEB-INF/jsp/manageUsers.jsp
new file mode 100644
index 0000000..99f0677
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/jsp/manageUsers.jsp
@@ -0,0 +1,57 @@
+<%--
+  ~ 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.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="jsec" uri="http://www.jsecurity.org/tags" %>
+
+<html>
+<head>
+    <title>JSecurity Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+
+<div id="bigbox">
+    <div class="title clearfix"><div style="float: left">JSecurity Sample App - Manage Users</div><div class="info" >Logged in as ${currentUser.username} (<a href="<c:url value="/s/logout"/>">Logout</a>)</div></div>
+
+
+    <div class="content">
+
+        <table id="manageUsers">
+            <tr>
+                <th>Username</th>
+                <th>Email</th>
+                <th>Actions</th>
+            </tr>
+            <c:forEach var="user" items="${users}">
+            <tr>
+                <td>${user.username}</td>
+                <td>${user.email}</td>
+                <td><a href="<c:url value="/s/editUser?userId=${user.id}"/>">Edit</a><c:if test="${user.id ne 1}">&nbsp;|&nbsp;<a href="<c:url value="/s/deleteUser?userId=${user.id}"/>">Delete</a></c:if>
+                </td>
+            </tr>
+            </c:forEach>
+        </table>
+
+        <p>Return to <a href="<c:url value="/s/home"/>">Home</a></p>
+    </div>
+
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/jsp/signup.jsp b/samples/spring-hibernate/web/WEB-INF/jsp/signup.jsp
new file mode 100644
index 0000000..565e435
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/jsp/signup.jsp
@@ -0,0 +1,49 @@
+<%--
+  ~ 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.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<html>
+<head>
+    <title>JSecurity Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+    <div id="box">
+        <div class="title">JSecurity Sample App - Signup</div>
+
+        <div class="content">
+            <form:form modelAttribute="signupCommand">
+
+                <form:errors path="*" element="div" cssClass="errors"/>
+
+                <div><div class="form-label">Username:</div><form:input path="username"/></div>
+                <div><div class="form-label">Email:</div><form:input path="email"/></div>
+                <div><div class="form-label">Password:</div><form:password path="password"/></div>
+                <div><input type="button" onclick="document.location.href='<c:url value="/s/login"/>'" value="Cancel"/>&nbsp;<input type="submit" value="Signup"/></div>
+            </form:form>
+        </div>
+    </div>
+
+    <script type="text/javascript">
+        document.getElementById('username').focus();
+    </script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/sprhib-servlet.xml b/samples/spring-hibernate/web/WEB-INF/sprhib-servlet.xml
new file mode 100644
index 0000000..fd4ae4b
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/sprhib-servlet.xml
@@ -0,0 +1,53 @@
+<?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.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:p="http://www.springframework.org/schema/p"
+    xmlns:context="http://www.springframework.org/schema/context"
+    xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+        http://www.springframework.org/schema/context
+        http://www.springframework.org/schema/context/spring-context-2.5.xsd">
+
+    <!-- Enable annotation component scanning and autowiring of web package -->
+    <context:annotation-config/>
+    <context:component-scan base-package="org.jsecurity.samples.sprhib.web"/>
+
+    <!-- Required for security annotations to work in this servlet -->
+    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
+    <bean class="org.jsecurity.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/>
+
+
+    <!-- Enable annotation-based controllers using @Controller annotations -->
+    <bean id="annotationUrlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
+        <property name="interceptors" ref="currentUserInterceptor"/>
+    </bean>
+
+    <bean id="annotationMethodHandlerAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
+
+    <!-- All views are JSPs loaded from /WEB-INF/jsp -->
+    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
+        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
+        <property name="prefix" value="/WEB-INF/jsp/"/>
+        <property name="suffix" value=".jsp"/>
+    </bean>    
+
+</beans>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/WEB-INF/web.xml b/samples/spring-hibernate/web/WEB-INF/web.xml
new file mode 100644
index 0000000..0a0f39d
--- /dev/null
+++ b/samples/spring-hibernate/web/WEB-INF/web.xml
@@ -0,0 +1,130 @@
+<?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.
+  -->
+<web-app version="2.4"
+         xmlns="http://java.sun.com/xml/ns/j2ee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+    <!-- ===================================================================
+ -  Context parameters
+ -  =================================================================== -->
+    <context-param>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>
+            /WEB-INF/applicationContext.xml
+        </param-value>
+    </context-param>
+
+    <!--
+    - Key of the system property that should specify the root directory of this
+    - web app. Applied by WebAppRootListener or Log4jConfigListener.
+    -->
+    <context-param>
+        <param-name>webAppRootKey</param-name>
+        <param-value>jsecurity-spring-hibernate-sample.webapp.root</param-value>
+    </context-param>
+
+    <!-- ===================================================================
+ -  Servlet listeners
+ -  =================================================================== -->
+    <listener>
+        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
+    </listener>
+    <listener>
+        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+    </listener>
+
+    <!-- ===================================================================
+ -  Filters
+ -  =================================================================== -->
+    <filter>
+        <filter-name>openSessionInViewFilter</filter-name>
+        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
+    </filter>
+
+    <filter>
+        <filter-name>JSecurityFilter</filter-name>
+        <filter-class>org.jsecurity.spring.SpringJSecurityFilter</filter-class>
+        <init-param>
+            <param-name>config</param-name>
+            <param-value>
+
+                # The JSecurityFilter configuration is very powerful and flexible, while still remaining succinct.
+                # Please read the comprehensive example, with full comments and explanations, in the JavaDoc:
+                #
+                # http://www.jsecurity.org/api/org/jsecurity/web/servlet/JSecurityFilter.html
+
+                # If you'd prefer to not have this configuration in web.xml, you can create a file called jsecurity.ini
+                # in the root of your classpath and JSecurity will automatically pick it up.
+
+                [filters]
+                # Override the authentication filter to pass thru so we can handle login logic in our controller
+                authc = org.jsecurity.web.filter.authc.PassThruAuthenticationFilter
+                jsecurity.loginUrl = /s/login
+                jsecurity.unauthorizedUrl = /unauthorized.jsp
+                authc.successUrl = /s/home
+
+                [urls]
+                /s/signup=anon
+                /s/manageUsers=perms[user:manage]
+                /s/**=authc
+            </param-value>
+        </init-param>
+
+    </filter>
+
+    <filter-mapping>
+        <filter-name>openSessionInViewFilter</filter-name>
+        <url-pattern>/s/*</url-pattern>
+    </filter-mapping>
+
+    <filter-mapping>
+        <filter-name>JSecurityFilter</filter-name>
+        <url-pattern>/s/*</url-pattern>
+    </filter-mapping>
+
+    <!-- ===================================================================
+ -  Servlets
+ -  =================================================================== -->
+    <servlet>
+        <servlet-name>sprhib</servlet-name>
+        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>sprhib</servlet-name>
+        <url-pattern>/s/*</url-pattern>
+    </servlet-mapping>
+
+
+    <!-- ===================================================================
+     -  Welcome file list
+     -  =================================================================== -->
+   <welcome-file-list>
+       <welcome-file>index.jsp</welcome-file>
+   </welcome-file-list>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/unauthorized.jsp</location>
+    </error-page>
+
+</web-app>
diff --git a/samples/spring-hibernate/web/index.jsp b/samples/spring-hibernate/web/index.jsp
new file mode 100644
index 0000000..56e49ec
--- /dev/null
+++ b/samples/spring-hibernate/web/index.jsp
@@ -0,0 +1,22 @@
+<%--
+  ~ 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.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+<%-- Redirect to index page --%>
+<c:redirect url="/s/home"/>
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/styles/sample.css b/samples/spring-hibernate/web/styles/sample.css
new file mode 100644
index 0000000..d227599
--- /dev/null
+++ b/samples/spring-hibernate/web/styles/sample.css
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+body { background: #f6f6f6}
+body, td, th { font-family:"Lucida Grande","Lucida Sans Unicode",Arial,Verdana,sans-serif; color: #333; font-size: 12px; }
+#box { width: 500px; }
+#bigbox { width: 940px; }
+#bigbox, #box { margin: 30px; padding: 0; border: thin black solid; background: #fff}
+#bigbox .title, #box .title { display: block; margin: 0 0 5px 0; padding: 5px 5px 5px 15px; background: #ddd; font-size: 18px}
+#bigbox .title .info { float: right; padding-right: 10px; font-size: 12px}
+
+.errors { color: red; padding: 10px 0 10px 0; }
+.form-label { float: left; width: 100px;}
+
+.content { padding: 10px;}
+.content div { padding: 5px; 0 5px 0 }
+
+#manageUsers { width: 800px }
+#manageUsers th { text-align: left }
+
+a:active, a:visited, a:link { color: #6666DD; text-decoration: none; }
+a:hover { color: #6666FF; text-decoration: underline; }
+
+.clearfix:after {content:".";display:block;height:0;clear:both;visibility:hidden;}
+.clearfix {display:inline-block;}
+* html .clearfix {height:1%;}
+.clearfix {display:block;}
\ No newline at end of file
diff --git a/samples/spring-hibernate/web/unauthorized.jsp b/samples/spring-hibernate/web/unauthorized.jsp
new file mode 100644
index 0000000..b46b28d
--- /dev/null
+++ b/samples/spring-hibernate/web/unauthorized.jsp
@@ -0,0 +1,29 @@
+<%--
+  ~ 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.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<html>
+<head>
+    <title>JSecurity Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+<h3>Unauthorized</h3>
+<p>You are not authorized to access the requested page.  Please return to the <a href="<c:url value="/s/home"/>">homepage</a>.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/web/src/org/jsecurity/web/attr/CookieAttribute.java b/web/src/org/jsecurity/web/attr/CookieAttribute.java
index d23a365..d5d4e0a 100644
--- a/web/src/org/jsecurity/web/attr/CookieAttribute.java
+++ b/web/src/org/jsecurity/web/attr/CookieAttribute.java
@@ -170,6 +170,29 @@
         return path;
     }
 
+
+    /**
+     * Returns the Cookie's calculated path setting.  If {@link Cookie#getPath() path} <tt>null</tt>, then the
+     * <tt>request</tt>'s {@link javax.servlet.http.HttpServletRequest#getContextPath() context path}
+     * will be returned. If getContextPath() is the empty string or null then the ROOT_PATH constant is returned.
+     * <p/>
+     * <p>The default is <code>null</code>.</p>
+     *
+     * @return the path to be used as the path when the cookie is created or removed.
+     */
+    public String calculatePath(HttpServletRequest request) {
+        String calculatePath = getPath() != null ? getPath() : request.getContextPath();
+
+        //fix for http://issues.apache.org/jira/browse/JSEC-34:
+        calculatePath = StringUtils.clean(calculatePath);
+        if (calculatePath == null) {
+            calculatePath = ROOT_PATH;
+        }
+        log.trace ("calculatePath: returning=" + calculatePath);
+        return calculatePath;
+    }
+
+
     /**
      * Sets the Cookie's {@link Cookie#getPath() path} setting.  If the argument is <tt>null</tt>, the <tt>request</tt>'s
      * {@link javax.servlet.http.HttpServletRequest#getContextPath() context path} will be used.
@@ -263,7 +286,7 @@
 
         String name = getName();
         int maxAge = getMaxAge();
-        String path = getPath() != null ? getPath() : request.getContextPath();
+        String path = calculatePath(request);
 
         //fix for http://issues.apache.org/jira/browse/JSEC-34:
         path = StringUtils.clean(path);
@@ -280,6 +303,7 @@
         }
 
         response.addCookie(cookie);
+        
         if (log.isTraceEnabled()) {
             log.trace("Added Cookie [" + name + "] to path [" + path + "] with value [" +
                     stringValue + "] to the HttpServletResponse.");
@@ -289,13 +313,17 @@
     public void removeValue(ServletRequest servletRequest, ServletResponse response) {
         HttpServletRequest request = toHttp(servletRequest);
         Cookie cookie = getCookie(request, getName());
+
         if (cookie != null) {
             cookie.setMaxAge(0);
+            cookie.setValue("forgetme");
             //JSEC-94: Must set the path on the outgoing cookie (some browsers don't retain it from the
             //retrieved cookie?)
-            cookie.setPath(getPath() == null ? request.getContextPath() : getPath());
+            // my testing shows none of these browsers will remove cookie if setPath() is not invoked: FF3, Chrome, IE7, Safari windows
+            cookie.setPath(calculatePath(request));
             cookie.setSecure(isSecure());
             toHttp(response).addCookie(cookie);
+            log.trace("Removed cookie[" + getName() + "] with path [" + calculatePath(request) + "] from HttpServletResponse.");
         }
     }
 }
diff --git a/web/src/org/jsecurity/web/filter/PathConfigProcessor.java b/web/src/org/jsecurity/web/filter/PathConfigProcessor.java
index 1eb6cf0..5c84b2f 100644
--- a/web/src/org/jsecurity/web/filter/PathConfigProcessor.java
+++ b/web/src/org/jsecurity/web/filter/PathConfigProcessor.java
@@ -18,6 +18,8 @@
  */
 package org.jsecurity.web.filter;
 
+import javax.servlet.Filter;
+
 /**
  * A PathConfigProcessor processes configuration entries on a per path (per url) basis.
  *
@@ -28,5 +30,5 @@
 
     //TODO - complete JavaDoc
 
-    void processPathConfig(String path, String config);
+    Filter processPathConfig(String path, String config);
 }
diff --git a/web/src/org/jsecurity/web/filter/PathMatchingFilter.java b/web/src/org/jsecurity/web/filter/PathMatchingFilter.java
index ab0173c..32ef50a 100644
--- a/web/src/org/jsecurity/web/filter/PathMatchingFilter.java
+++ b/web/src/org/jsecurity/web/filter/PathMatchingFilter.java
@@ -26,6 +26,7 @@
 import org.jsecurity.web.WebUtils;
 import org.jsecurity.web.servlet.AdviceFilter;
 
+import javax.servlet.Filter;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import java.util.LinkedHashMap;
@@ -76,14 +77,16 @@
      *
      * @param path   the application context path to match for executing this filter.
      * @param config the specified for <em>this particular filter only</em> for the given <code>path</code>
+     * @return this configured filter.
      */
-    public void processPathConfig(String path, String config) {
+    public Filter processPathConfig(String path, String config) {
         String[] values = null;
         if (config != null) {
             values = split(config);
         }
 
         this.appliedPaths.put(path, values);
+        return this;
     }
 
     /**
diff --git a/web/test/org/jsecurity/web/attr/CookieAttributeTest.java b/web/test/org/jsecurity/web/attr/CookieAttributeTest.java
index 6855bc3..5c284ae 100644
--- a/web/test/org/jsecurity/web/attr/CookieAttributeTest.java
+++ b/web/test/org/jsecurity/web/attr/CookieAttributeTest.java
@@ -55,7 +55,7 @@
 
         expect(mockRequest.getCookies()).andReturn(cookies);
         //no path set on the cookie, so we expect to retrieve it from the context path
-        expect(mockRequest.getContextPath()).andReturn("/somepath");
+        expect(mockRequest.getContextPath()).andReturn("/somepath").times(2);
         mockResponse.addCookie(cookie);
         replay(mockRequest);
         replay(mockResponse);
@@ -121,4 +121,29 @@
         });
         return null;
     }
+
+    @Test
+    //Verifies fix for JSEC-64
+    public void testRemoveValueWithNullContext() throws Exception {
+
+        Cookie cookie = new Cookie("test", "blah");
+        cookie.setMaxAge(2351234); //doesn't matter what the time is
+        Cookie[] cookies = new Cookie[]{cookie};
+
+        expect(mockRequest.getCookies()).andReturn(cookies);
+        //no path set on the cookie, so we expect to retrieve it from the context path
+        expect(mockRequest.getContextPath()).andReturn(null).times(2);
+        mockResponse.addCookie(cookie);
+        replay(mockRequest);
+        replay(mockResponse);
+
+        cookieAttribute.removeValue(mockRequest, mockResponse);
+
+        verify(mockRequest);
+        verify(mockResponse);
+
+        assertTrue(cookie.getMaxAge() == 0);
+        assertTrue(cookie.getPath().equals("/"));
+    }
+
 }