From 5d0af207adc99bba2b4ed7d34670a2e3e4ae0d0f Mon Sep 17 00:00:00 2001
From: david_page <david_page@localhost>
Date: Mon, 12 Mar 2007 13:38:34 +0000
Subject: [PATCH] The following update to core.PasswordPolicyState.java, along with some small changes to consumers of its API are the result of a review in preparation for password policy state management extended operation (issue 579).

---
 opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java                   | 3070 +++++++++++++++++++++++++---------------------------------
 opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java |    1 
 opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java                         |   30 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java                       |    3 
 4 files changed, 1,337 insertions(+), 1,767 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java
index 51909be..2c75910 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java
@@ -1542,7 +1542,7 @@
 
 
               pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
-              pwPolicyState.clearAuthFailureTimes();
+              pwPolicyState.clearFailureLockout();
 
               if (isFirstWarning)
               {
@@ -1574,21 +1574,15 @@
               setResultCode(ResultCode.INVALID_CREDENTIALS);
               setAuthFailureReason(msgID, message);
 
-              int maxAllowedFailures
-                   = pwPolicyState.getPolicy().getLockoutFailureCount();
-              if (maxAllowedFailures > 0)
+              if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
               {
                 pwPolicyState.updateAuthFailureTimes();
-                if (pwPolicyState.getAuthFailureTimes().size() >=
-                    maxAllowedFailures)
+                if (pwPolicyState.lockedDueToFailures())
                 {
-                  pwPolicyState.lockDueToFailures();
-
                   AccountStatusNotificationType notificationType;
 
-                  int lockoutDuration
-                       = pwPolicyState.getPolicy().getLockoutDuration();
-                  if (lockoutDuration > 0)
+                  int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
+                  if (lockoutDuration > -1)
                   {
                     notificationType = AccountStatusNotificationType.
                                             ACCOUNT_TEMPORARILY_LOCKED;
@@ -2073,23 +2067,17 @@
               if (saslHandler.isPasswordBased(saslMechanism))
               {
 
-                int maxAllowedFailures
-                     = pwPolicyState.getPolicy().getLockoutFailureCount();
-                if (maxAllowedFailures > 0)
+                if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
                 {
                   pwPolicyState.updateAuthFailureTimes();
-                  if (pwPolicyState.getAuthFailureTimes().size() >=
-                      maxAllowedFailures)
+                  if (pwPolicyState.lockedDueToFailures())
                   {
-                    pwPolicyState.lockDueToFailures();
-
                     AccountStatusNotificationType notificationType;
                     int msgID;
                     String message;
 
-                    int lockoutDuration
-                         = pwPolicyState.getPolicy().getLockoutDuration();
-                    if (lockoutDuration > 0)
+                    int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
+                    if (lockoutDuration > -1)
                     {
                       notificationType = AccountStatusNotificationType.
                                               ACCOUNT_TEMPORARILY_LOCKED;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
index 8623ba4..ea571a1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -1286,7 +1286,6 @@
             // Update the password policy state attributes in the user's entry.
             // If the modification fails, then these changes won't be applied.
             pwPolicyState.setPasswordChangedTime();
-            pwPolicyState.clearAuthFailureTimes();
             pwPolicyState.clearFailureLockout();
             pwPolicyState.clearGraceLoginTimes();
             pwPolicyState.clearWarnedTime();
@@ -1297,7 +1296,7 @@
               pwPolicyState.setMustChangePassword(! selfChange);
             }
 
-            if (pwPolicyState.getRequiredChangeTime() > 0)
+            if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
             {
               pwPolicyState.setRequiredChangeTime();
             }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index c604154..e13717b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -85,106 +85,83 @@
 {
 
 
-
-  // Indicates whether to debug password policy processing performed wth this
-  // state object.
-  private boolean debug;
+  // The user entry with which this state information is associated.
+  private final Entry userEntry;
 
   // Indicates whether the user entry itself should be updated or if the updates
   // should be stored as modifications.
-  private boolean updateEntry;
+  private final boolean updateEntry;
 
-  // Indicates whether an expiration warning message should be sent if the
-  // authentication is successful.
-  private boolean sendExpirationWarning;
+  // Indicates whether to debug password policy processing performed wth this
+  // state object.
+  private final boolean debug;
 
-  // Indicates whether a grace login will be used if the authentication is
-  // successful.
-  private boolean useGraceLogin;
+  // The string representation of the user's DN.
+  private final String userDNString;
+
+  // The password policy with which the account is associated.
+  private final PasswordPolicy passwordPolicy;
+
+  // The current time for use in all password policy calculations.
+  private final long currentTime;
+
+  // The time that the user's password was last changed.
+  private long passwordChangedTime = Long.MIN_VALUE;
 
   // Indicates whether the user's account is expired.
-  private ConditionResult isAccountExpired;
+  private ConditionResult isAccountExpired = ConditionResult.UNDEFINED;
 
   // Indicates whether the user's account is disabled.
-  private ConditionResult isDisabled;
+  private ConditionResult isDisabled = ConditionResult.UNDEFINED;
 
   // Indicates whether the user's password is expired.
-  private ConditionResult isPasswordExpired;
+  private ConditionResult isPasswordExpired = ConditionResult.UNDEFINED;
 
   // Indicates whether the warning to send to the client would be the first
   // warning for the user.
-  private ConditionResult isFirstWarning;
+  private ConditionResult isFirstWarning = ConditionResult.UNDEFINED;
 
   // Indicates whether the user's account is locked by the idle lockout.
-  private ConditionResult isIdleLocked;
-
-  // Indicates whether the user's account is locked by administrative reset.
-  private ConditionResult isResetLocked;
+  private ConditionResult isIdleLocked = ConditionResult.UNDEFINED;
 
   // Indicates whether the user may use a grace login if the password is expired
   // and there are one or more grace logins remaining.
-  private ConditionResult mayUseGraceLogin;
+  private ConditionResult mayUseGraceLogin = ConditionResult.UNDEFINED;
 
   // Indicates whether the user's password must be changed.
-  private ConditionResult mustChangePassword;
+  private ConditionResult mustChangePassword = ConditionResult.UNDEFINED;
 
   // Indicates whether the user should be warned of an upcoming expiration.
-  private ConditionResult shouldWarn;
-
-  // The user entry with which this state information is associated.
-  private Entry userEntry;
+  private ConditionResult shouldWarn = ConditionResult.UNDEFINED;
 
   // The number of seconds until the user's account is automatically unlocked.
-  private int secondsUntilUnlock;
-
-  // The number of seconds until the user's password expires.
-  private int secondsUntilExpiration;
-
-  // The set of modifications that should be applied to the user's entry.
-  private LinkedList<Modification> modifications;
+  private int secondsUntilUnlock = Integer.MIN_VALUE;
 
   // The set of authentication failure times for this user.
-  private List<Long> authFailureTimes;
+  private List<Long> authFailureTimes = null;
 
   // The set of grace login times for this user.
-  private List<Long> graceLoginTimes;
-
-  // The time that the user's account was created.
-  private long createTime;
-
-  // The current time for use in all password policy calculations.
-  private long currentTime;
+  private List<Long> graceLoginTimes = null;
 
   // The time that the user's password should expire (or did expire).
-  private long expirationTime;
+  private long expirationTime = Long.MIN_VALUE;
 
   // The time that the user's entry was locked due to too many authentication
   // failures.
-  private long failureLockedTime;
-
-  // The time that the user's entry was locked due to the idle lockout.
-  private long idleLockedTime;
+  private long failureLockedTime = Long.MIN_VALUE;
 
   // The time that the user last authenticated to the Directory Server.
-  private long lastLoginTime;
-
-  // The time that the user's password was last changed.
-  private long passwordChangedTime;
+  private long lastLoginTime = Long.MIN_VALUE;
 
   // The last required change time with which the user complied.
-  private long requiredChangeTime;
+  private long requiredChangeTime = Long.MIN_VALUE;
 
   // The time that the user was first warned about an upcoming expiration.
-  private long warnedTime;
+  private long warnedTime = Long.MIN_VALUE;
 
-  // The password policy with which the account is associated.
-  private PasswordPolicy passwordPolicy;
-
-  // The string representation of the current time.
-  private String currentGeneralizedTime;
-
-  // The string representation of the user's DN.
-  private String userDNString;
+  // The set of modifications that should be applied to the user's entry.
+  private LinkedList<Modification> modifications
+       = new LinkedList<Modification>();
 
 
 
@@ -204,62 +181,37 @@
    */
   public PasswordPolicyState(Entry userEntry, boolean updateEntry,
                              boolean debug)
-         throws DirectoryException
+       throws DirectoryException
   {
     this.userEntry   = userEntry;
     this.updateEntry = updateEntry;
     this.debug       = debug;
 
-    userDNString           = userEntry.getDN().toString();
-    passwordPolicy         = getPasswordPolicyInternal();
-    currentGeneralizedTime = TimeThread.getGeneralizedTime();
-    currentTime            = TimeThread.getTime();
-    modifications          = new LinkedList<Modification>();
-    isDisabled             = ConditionResult.UNDEFINED;
-    isAccountExpired       = ConditionResult.UNDEFINED;
-    isPasswordExpired      = ConditionResult.UNDEFINED;
-    isFirstWarning         = ConditionResult.UNDEFINED;
-    isIdleLocked           = ConditionResult.UNDEFINED;
-    isResetLocked          = ConditionResult.UNDEFINED;
-    mayUseGraceLogin       = ConditionResult.UNDEFINED;
-    mustChangePassword     = ConditionResult.UNDEFINED;
-    shouldWarn             = ConditionResult.UNDEFINED;
-    expirationTime         = Long.MIN_VALUE;
-    failureLockedTime      = Long.MIN_VALUE;
-    idleLockedTime         = Long.MIN_VALUE;
-    lastLoginTime          = Long.MIN_VALUE;
-    requiredChangeTime     = Long.MIN_VALUE;
-    warnedTime             = Long.MIN_VALUE;
-    authFailureTimes       = null;
-    sendExpirationWarning  = false;
-    useGraceLogin          = false;
-    secondsUntilExpiration = Integer.MIN_VALUE;
-    secondsUntilUnlock     = Integer.MIN_VALUE;
-
-
-    // Get the time that the user's account was created.
-    AttributeType type =
-         DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC);
-    if (type == null)
-    {
-      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_CREATE_TIMESTAMP);
-    }
-
-    createTime = getGeneralizedTime(type);
-
+    userDNString     = userEntry.getDN().toString();
+    passwordPolicy   = getPasswordPolicyInternal(this.userEntry, this.debug);
+    currentTime      = TimeThread.getTime();
 
     // Get the password changed time for the user.
-    type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
+    AttributeType type
+         = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
     if (type == null)
     {
       type = DirectoryServer.getDefaultAttributeType(
-                                  OP_ATTR_PWPOLICY_CHANGED_TIME);
+           OP_ATTR_PWPOLICY_CHANGED_TIME);
     }
 
     passwordChangedTime = getGeneralizedTime(type);
     if (passwordChangedTime <= 0)
     {
-      passwordChangedTime = createTime;
+      // Get the time that the user's account was created.
+      AttributeType createTimeType
+           = DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC);
+      if (createTimeType == null)
+      {
+        createTimeType
+            = DirectoryServer.getDefaultAttributeType(OP_ATTR_CREATE_TIMESTAMP);
+      }
+      passwordChangedTime = getGeneralizedTime(createTimeType);
 
       if (passwordChangedTime <= 0)
       {
@@ -267,9 +219,8 @@
 
         if (debug)
         {
-          debugWarning(
-              "Could not determine password changed time " +
-                           "for user %s", userDNString);
+          debugWarning("Could not determine password changed time for user %s.",
+                       userDNString);
         }
       }
     }
@@ -278,42 +229,36 @@
 
 
   /**
-   * Retrieves the password policy for the user.
+   * Retrieves the password policy for the user. If the user entry contains the
+   * ds-pwp-password-policy-dn attribute (whether real or virtual), that
+   * password policy is returned, otherwise the default password policy is
+   * returned.
+   *
+   * @param  userEntry    The user entry.
+   * @param  debug        Indicates whether to enable debugging for the
+   *                      operations performed.
    *
    * @return  The password policy for the user.
    *
    * @throws  DirectoryException  If a problem occurs while attempting to
    *                              determine the password policy for the user.
    */
-  private PasswordPolicy getPasswordPolicyInternal()
-          throws DirectoryException
+  private static PasswordPolicy getPasswordPolicyInternal(Entry userEntry,
+                                                          boolean debug)
+       throws DirectoryException
   {
-    // See if the user entry contains the ds-pwp-password-policy-dn attribute to
-    // select a custom objectclass (whether real or virtual).
+    String userDNString = userEntry.getDN().toString();
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_POLICY_DN, true);
 
     List<Attribute> attrList = userEntry.getAttribute(type);
-    if ((attrList == null) || attrList.isEmpty())
+    if (attrList != null)
     {
-      // There is no policy subentry defined, so we'll use the default.
-      if (debug)
+      for (Attribute a : attrList)
       {
-        if (debugEnabled())
-        {
-          debugInfo("Using the default password policy for user %s",
-                    userDNString);
-        }
-      }
+        if(a.getValues().isEmpty()) continue;
 
-      return DirectoryServer.getDefaultPasswordPolicy();
-    }
-
-
-    for (Attribute a : attrList)
-    {
-      for (AttributeValue v : a.getValues())
-      {
+        AttributeValue v = a.getValues().iterator().next();
         DN subentryDN;
         try
         {
@@ -328,11 +273,10 @@
 
           if (debug)
           {
-            debugError(
-                "Could not parse password policy subentry " +
-                    "DN %s for user %s: %s",
-                v.getStringValue(), userDNString,
-                stackTraceToSingleLineString(e));
+            debugError("Could not parse password policy subentry DN %s " +
+                 "for user %s: %s",
+                       v.getStringValue(), userDNString,
+                       stackTraceToSingleLineString(e));
           }
 
           int    msgID   = MSGID_PWPSTATE_CANNOT_DECODE_SUBENTRY_VALUE_AS_DN;
@@ -347,9 +291,8 @@
         {
           if (debug)
           {
-            debugError(
-                "Password policy subentry %s for user %s " +
-                           "is not defined in the Directory Server.",
+            debugError("Password policy subentry %s for user %s " +
+                 "is not defined in the Directory Server.",
                        String.valueOf(subentryDN), userDNString);
           }
 
@@ -357,33 +300,30 @@
           String message = getMessage(msgID, userDNString,
                                       String.valueOf(subentryDN));
           throw new DirectoryException(
-                         DirectoryServer.getServerErrorResultCode(), message,
-                         msgID);
+               DirectoryServer.getServerErrorResultCode(), message,
+               msgID);
         }
-        else
+
+        if (debug)
         {
-          if (debug)
-          {
             if (debugEnabled())
             {
-              debugInfo("Using password policy subentry %s for user " +
-                  "%s.", String.valueOf(subentryDN), userDNString);
+              debugInfo("Using password policy subentry %s for user %s.",
+                        String.valueOf(subentryDN), userDNString);
             }
           }
 
-          return policy;
-        }
+        return policy;
       }
     }
 
-
-    // This shouldn't happen, but if it does then use the default.
+    // There is no policy subentry defined: use the default.
     if (debug)
     {
       if (debugEnabled())
       {
-        debugInfo("Falling back to the default password policy for " +
-            "user %s", userDNString);
+        debugInfo("Using the default password policy for user %s",
+                  userDNString);
       }
     }
 
@@ -392,52 +332,52 @@
 
 
 
-  /**
-   * Retrieves the value of the specified attribute as a string.
-   *
-   * @param  attributeType  The attribute type whose value should be retrieved.
-   *
-   * @return  The value of the specified attribute as a string, or
-   *          <CODE>null</CODE> if there is no such value.
-   */
+   /**
+    * Retrieves the value of the specified attribute as a string.
+    *
+    * @param  attributeType  The attribute type whose value should be retrieved.
+    *
+    * @return  The value of the specified attribute as a string, or
+    *          <CODE>null</CODE> if there is no such value.
+    */
   private String getValue(AttributeType attributeType)
   {
+    String stringValue = null;
+
     List<Attribute> attrList = userEntry.getAttribute(attributeType);
-    if ((attrList == null) || attrList.isEmpty())
+    if (attrList != null)
     {
-      if (debug)
+      for (Attribute a : attrList)
+      {
+        if (a.getValues().isEmpty()) continue;
+
+        stringValue = a.getValues().iterator().next().getStringValue();
+        break ;
+      }
+    }
+
+    if (debug)
+    {
+      if (stringValue == null)
       {
         if (debugEnabled())
         {
           debugInfo("Returning null because attribute %s does not " +
-              "exist in user entry %s", attributeType.getNameOrOID(),
-                                        userDNString);
+              "exist in user entry %s",
+                    attributeType.getNameOrOID(), userDNString);
         }
       }
-
-      return null;
-    }
-
-    for (Attribute a : attrList)
-    {
-      for (AttributeValue v : a.getValues())
+      else
       {
-        String stringValue = v.getStringValue();
-
-        if (debug)
+        if (debugEnabled())
         {
-          if (debugEnabled())
-          {
-            debugInfo("Returning value %s for user %s", stringValue,
-                      userDNString);
-          }
+          debugInfo("Returning value %s for user %s",
+                    stringValue, userDNString);
         }
-
-        return stringValue;
       }
     }
 
-    return null;
+    return stringValue;
   }
 
 
@@ -457,31 +397,20 @@
   private long getGeneralizedTime(AttributeType attributeType)
           throws DirectoryException
   {
+    long timeValue = -1 ;
+
     List<Attribute> attrList = userEntry.getAttribute(attributeType);
-    if ((attrList == null) || attrList.isEmpty())
+    if (attrList != null)
     {
-      if (debug)
+      for (Attribute a : attrList)
       {
-        if (debugEnabled())
-        {
-          debugInfo("Returning -1 because attribute %s does not " +
-              "exist in user entry %s", attributeType.getNameOrOID(),
-                                        userDNString);
-        }
-      }
+        if (a.getValues().isEmpty()) continue;
 
-      return -1;
-    }
-
-
-    for (Attribute a : attrList)
-    {
-      for (AttributeValue v  : a.getValues())
-      {
+        AttributeValue v = a.getValues().iterator().next();
         try
         {
-          return GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
-                                            v.getNormalizedValue());
+          timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                          v.getNormalizedValue());
         }
         catch (Exception e)
         {
@@ -492,11 +421,10 @@
 
           if (debug)
           {
-            debugWarning(
-                "Unable to decode value %s for attribute " +
-                    "%s in user entry %s: %s",
-                v.getStringValue(),
-                attributeType.getNameOrOID(), userDNString);
+            debugWarning("Unable to decode value %s for attribute %s " +
+                 "in user entry %s: %s",
+                         v.getStringValue(), attributeType.getNameOrOID(),
+                         userDNString, stackTraceToSingleLineString(e));
           }
 
           int msgID = MSGID_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME;
@@ -506,21 +434,25 @@
           throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                        message, msgID, e);
         }
+        break ;
       }
     }
 
-
     if (debug)
     {
-      if (debugEnabled())
+      if (timeValue == -1)
       {
-        debugInfo("Returning -1 for attribute %s in user entry %s " +
-            "because all options have been exhausted.",
-                  attributeType.getNameOrOID(), userDNString);
+        if (debugEnabled())
+        {
+          debugInfo("Returning -1 because attribute %s does not " +
+              "exist in user entry %s",
+                    attributeType.getNameOrOID(), userDNString);
+        }
       }
+      // FIXME: else to be consistent...
     }
 
-    return -1;
+    return timeValue;
   }
 
 
@@ -544,9 +476,46 @@
     ArrayList<Long> timeValues = new ArrayList<Long>();
 
     List<Attribute> attrList = userEntry.getAttribute(attributeType);
-    if ((attrList == null) || attrList.isEmpty())
+    if (attrList != null)
     {
-      if (debug)
+      for (Attribute a : attrList)
+      {
+        for (AttributeValue v : a.getValues())
+        {
+          try
+          {
+            timeValues.add(GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                                       v.getNormalizedValue()));
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            if (debug)
+            {
+              debugWarning("Unable to decode value %s for attribute %s" +
+                   "in user entry %s: %s",
+                           v.getStringValue(), attributeType.getNameOrOID(),
+                           userDNString, stackTraceToSingleLineString(e));
+            }
+
+            int msgID = MSGID_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME;
+            String message = getMessage(msgID, v.getStringValue(),
+                                        attributeType.getNameOrOID(),
+                                        userDNString, String.valueOf(e));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID, e);
+          }
+        }
+      }
+    }
+
+    if (debug)
+    {
+      if (timeValues.isEmpty())
       {
         if (debugEnabled())
         {
@@ -555,47 +524,7 @@
                     attributeType.getNameOrOID(), userDNString);
         }
       }
-
-      return timeValues;
     }
-
-
-    for (Attribute a : attrList)
-    {
-      for (AttributeValue v  : a.getValues())
-      {
-        try
-        {
-          timeValues.add(GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
-                                                    v.getNormalizedValue()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          if (debug)
-          {
-            debugWarning(
-                "Unable to decode value %s for attribute " +
-                    "%s in user entry %s: %s",
-                v.getStringValue(),
-                attributeType.getNameOrOID(),
-                userDNString, e);
-          }
-
-          int msgID = MSGID_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME;
-          String message = getMessage(msgID, v.getStringValue(),
-                                      attributeType.getNameOrOID(),
-                                      userDNString, String.valueOf(e));
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-      }
-    }
-
     return timeValues;
   }
 
@@ -607,40 +536,26 @@
    *
    * @param  attributeType  The attribute type whose value should be parsed as a
    *                        Boolean.
-   * @param  defaultValue   The default value that should be used if the
-   *                        specified attribute does not exist.
    *
-   * @return  The requested Boolean value, or the default value if the specified
-   *          attribute does not exist with a Boolean value.
+   * @return  The attribute's value represented as a ConditionResult value, or
+   *          ConditionResult.UNDEFINED if the specified attribute does not
+   *          exist in the entry.
    *
    * @throws  DirectoryException  If the value cannot be decoded as a Boolean.
    */
-  private boolean getBoolean(AttributeType attributeType, boolean defaultValue)
+  private ConditionResult getBoolean(AttributeType attributeType)
           throws DirectoryException
   {
     List<Attribute> attrList = userEntry.getAttribute(attributeType);
-    if ((attrList == null) || attrList.isEmpty())
+    if (attrList != null)
     {
-      if (debug)
+      for (Attribute a : attrList)
       {
-        if (debugEnabled())
-        {
-          debugInfo("Returning default of %b because attribute " +
-              "%s does not exist in user entry %s",
-                    defaultValue, attributeType.getNameOrOID(),
-                    attributeType.getNameOrOID());
-        }
-      }
+        if (a.getValues().isEmpty()) continue;
 
-      return defaultValue;
-    }
+        String valueString
+             = toLowerCase(a.getValues().iterator().next().getStringValue());
 
-
-    for (Attribute a : attrList)
-    {
-      for (AttributeValue v  : a.getValues())
-      {
-        String valueString = toLowerCase(v.getStringValue());
         if (valueString.equals("true") || valueString.equals("yes") ||
             valueString.equals("on") || valueString.equals("1"))
         {
@@ -648,61 +563,58 @@
           {
             if (debugEnabled())
             {
-              debugInfo("Attribute %s resolves to true for user " +
-                  "entry %s", attributeType.getNameOrOID(), userDNString);
+              debugInfo("Attribute %s resolves to true for user entry %s",
+                        attributeType.getNameOrOID(), userDNString);
             }
           }
 
-          return true;
+          return ConditionResult.TRUE;
         }
-        else if (valueString.equals("false") || valueString.equals("no") ||
+
+        if (valueString.equals("false") || valueString.equals("no") ||
                  valueString.equals("off") || valueString.equals("0"))
         {
           if (debug)
           {
             if (debugEnabled())
             {
-              debugInfo("Attribute %s resolves to false for user " +
-                  "entry %s", attributeType.getNameOrOID(), userDNString);
+              debugInfo("Attribute %s resolves to false for user entry %s",
+                        attributeType.getNameOrOID(), userDNString);
             }
           }
 
-          return false;
+          return ConditionResult.FALSE;
         }
-        else
+
+        if (debug)
         {
-          if (debug)
-          {
-            debugError(
-                "Unable to resolve value %s for attribute " +
-                           "%s in user entry %us as a Boolean.",
+            debugError("Unable to resolve value %s for attribute %s " +
+                 "in user entry %s as a Boolean.",
                        valueString, attributeType.getNameOrOID(),
                        userDNString);
-          }
-
-          int msgID = MSGID_PWPSTATE_CANNOT_DECODE_BOOLEAN;
-          String message = getMessage(msgID, v.getStringValue(),
-                                      attributeType.getNameOrOID(),
-                                      userDNString);
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
         }
+
+        int msgID = MSGID_PWPSTATE_CANNOT_DECODE_BOOLEAN;
+        String message = getMessage(msgID, valueString,
+                                    attributeType.getNameOrOID(),
+                                    userDNString);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
       }
     }
 
-
     if (debug)
     {
       if (debugEnabled())
       {
-        debugInfo("Returning default of %b for attribute %s in " +
-            "user entry %s because all options have been " +
-            "exhausted.", defaultValue, attributeType.getNameOrOID(),
-                          userDNString);
+        debugInfo("Returning %s because attribute %s does not exist " +
+             "in user entry %s",
+                  ConditionResult.UNDEFINED.toString(),
+                  attributeType.getNameOrOID(), userDNString);
       }
     }
 
-    return defaultValue;
+    return ConditionResult.UNDEFINED;
   }
 
 
@@ -720,21 +632,6 @@
 
 
   /**
-   * Retrieves the set of modifications that correspond to changes made in
-   * password policy processing that may need to be applied to the user entry.
-   *
-   * @return  The set of modifications that correspond to changes made in
-   *          password policy processing that may need to be applied to the user
-   *          entry.
-   */
-  public LinkedList<Modification> getModifications()
-  {
-    return modifications;
-  }
-
-
-
-  /**
    * Retrieves the set of values for the password attribute from the user entry.
    *
    * @return  The set of values for the password attribute from the user entry.
@@ -743,9 +640,14 @@
   {
     List<Attribute> attrList =
          userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
-    for (Attribute a : attrList)
+    if (attrList != null)
     {
-      return a.getValues();
+      for (Attribute a : attrList)
+      {
+        if (a.getValues().isEmpty()) continue;
+
+        return a.getValues();
+      }
     }
 
     return new LinkedHashSet<AttributeValue>(0);
@@ -754,47 +656,6 @@
 
 
   /**
-   * Indicates whether the associated password policy requires that
-   * authentication be performed in a secure manner.
-   *
-   * @return  <CODE>true</CODE> if the associated password policy requires that
-   *          authentication be performed in a secure manner, or
-   *          <CODE>false</CODE> if not.
-   */
-  public boolean requireSecureAuthentication()
-  {
-    return passwordPolicy.requireSecureAuthentication();
-  }
-
-
-
-  /**
-   * Retrieves time that this password policy state object was created.
-   *
-   * @return  The time that this password policy state object was created.
-   */
-  public long getCurrentTime()
-  {
-    return currentTime;
-  }
-
-
-
-  /**
-   * Retrieves the generalized time representation of the time that this
-   * password policy state object was created.
-   *
-   * @return  The generalized time representation of the time that this
-   *          password policy state object was created.
-   */
-  public String getCurrentGeneralizedTime()
-  {
-    return currentGeneralizedTime;
-  }
-
-
-
-  /**
    * Sets a new value for the password changed time equal to the current time.
    */
   public void setPasswordChangedTime()
@@ -808,6 +669,8 @@
       }
     }
 
+    // passwordChangedTime is computed in the constructor from values in the
+    // entry.
     if (passwordChangedTime != currentTime)
     {
       passwordChangedTime = currentTime;
@@ -850,87 +713,71 @@
    */
   public boolean isDisabled()
   {
-    if ((isDisabled == null) || (isDisabled == ConditionResult.UNDEFINED))
-    {
-      AttributeType type =
-           DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
-      try
-      {
-        if (getBoolean(type, false))
-        {
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("User %s is administratively disabled.", userDNString);
-            }
-          }
-
-          isDisabled = ConditionResult.TRUE;
-          return true;
-        }
-        else
-        {
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("User %s is not administratively disabled.",
-                        userDNString);
-            }
-          }
-
-          isDisabled = ConditionResult.FALSE;
-          return false;
-        }
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        if (debug)
-        {
-          debugWarning(
-              "User %s is considered administratively disabled " +
-                  "because an error occurred while attempting to make " +
-                  "the determination: %s.",
-              userDNString, stackTraceToSingleLineString(e));
-        }
-
-        isDisabled = ConditionResult.TRUE;
-        return true;
-      }
-    }
-
-    if (isDisabled == ConditionResult.FALSE)
+    if (isDisabled != ConditionResult.UNDEFINED)
     {
       if (debug)
       {
         if (debugEnabled())
         {
-          debugInfo("Returning stored result of false for user %s",
-                    userDNString);
+          debugInfo("Returning stored result of %b for user %s",
+                    (isDisabled == ConditionResult.TRUE), userDNString);
         }
       }
 
-      return false;
+      return isDisabled == ConditionResult.TRUE;
     }
-    else
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
+    try
     {
+      isDisabled = getBoolean(type);
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      isDisabled = ConditionResult.TRUE;
       if (debug)
       {
-        if (debugEnabled())
-        {
-          debugInfo("Returning stored result of true for user %s",
-                    userDNString);
-        }
+          debugWarning("User %s is considered administratively disabled " +
+               "because an error occurred while attempting to make " +
+               "the determination: %s.",
+                       userDNString, stackTraceToSingleLineString(e));
       }
 
       return true;
     }
+
+    if (isDisabled == ConditionResult.UNDEFINED)
+    {
+      isDisabled = ConditionResult.FALSE;
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("User %s is not administratively disabled since the" +
+                          " attribute \"%s\" is not present in the entry.",
+                     userDNString, OP_ATTR_ACCOUNT_DISABLED);
+        }
+      }
+      return false;
+    }
+
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        debugInfo("User %s %s administratively disabled.",
+                  userDNString,
+                  ((isDisabled == ConditionResult.TRUE) ? " is" : " is not"));
+      }
+    }
+
+    return isDisabled == ConditionResult.TRUE;
   }
 
 
@@ -954,27 +801,27 @@
     }
 
 
+    if (isDisabled == isDisabled())
+    {
+      return; // requested state matches current state
+    }
+
+    this.isDisabled = ConditionResult.inverseOf(this.isDisabled);
+
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
-    LinkedHashSet<AttributeValue> values;
 
     if (isDisabled)
     {
-      if (this.isDisabled == ConditionResult.TRUE)
-      {
-        return;
-      }
-
-      this.isDisabled = ConditionResult.TRUE;
-      values = new LinkedHashSet<AttributeValue>(1);
-      values.add(new AttributeValue(type, String.valueOf(isDisabled)));
-
+      LinkedHashSet<AttributeValue> values
+           = new LinkedHashSet<AttributeValue>(1);
+      values.add(new AttributeValue(type, String.valueOf(true)));
       Attribute a = new Attribute(type, OP_ATTR_ACCOUNT_DISABLED, values);
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
 
       if (updateEntry)
       {
+        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+        attrList.add(a);
         userEntry.putAttribute(type, attrList);
       }
       else
@@ -984,15 +831,7 @@
     }
     else
     {
-      if (this.isDisabled == ConditionResult.FALSE)
-      {
-        return;
-      }
-
-      this.isDisabled = ConditionResult.FALSE;
-      values = new LinkedHashSet<AttributeValue>(1);
-      values.add(new AttributeValue(type, String.valueOf(isDisabled)));
-
+      // erase
       if (updateEntry)
       {
         userEntry.removeAttribute(type);
@@ -1015,252 +854,250 @@
    */
   public boolean isAccountExpired()
   {
-    if ((isAccountExpired == null) ||
-        (isAccountExpired == ConditionResult.UNDEFINED))
+    if (isAccountExpired != ConditionResult.UNDEFINED)
     {
-      AttributeType type =
-           DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
-                                            true);
-      try
-      {
-        long expirationTime = getGeneralizedTime(type);
-        if (expirationTime < 0)
-        {
-          // The user doesn't have an expiration time in their entry, so it
-          // can't be expired.
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("The account for user %s is not expired because " +
-                  "there is no expiration time in the user's entry.",
-              userDNString);
-            }
-          }
-
-          isAccountExpired = ConditionResult.FALSE;
-          return false;
-        }
-        else if (expirationTime > currentTime)
-        {
-          // The user does have an expiration time, but it hasn't arrived yet.
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("The account for user %s is not expired because the " +
-                  "expiration time has not yet arrived.", userDNString);
-            }
-          }
-
-          isAccountExpired = ConditionResult.FALSE;
-          return false;
-        }
-        else
-        {
-          // The user does have an expiration time, and it is in the past.
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("The account for user %s is expired because the " +
-                  "expiration time in that account has passed.", userDNString);
-            }
-          }
-
-          isAccountExpired = ConditionResult.TRUE;
-          return true;
-        }
-      }
-      catch (Exception e)
+      if(debug)
       {
         if (debugEnabled())
         {
-          debugCaught(DebugLogLevel.ERROR, e);
+          debugInfo("Returning stored result of %b for user %s",
+                    (isAccountExpired == ConditionResult.TRUE), userDNString);
         }
+      }
 
-        if (debug)
-        {
-          debugWarning(
-              "User %s is considered to have an expired account " +
-                  "because an error occurred while attempting to make " +
-                  "the determination: %s.",
+      return isAccountExpired == ConditionResult.TRUE;
+    }
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
+                                          true);
+
+    long expirationTime;
+    try
+    {
+      expirationTime = getGeneralizedTime(type);
+     }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      isAccountExpired = ConditionResult.TRUE;
+      if (debug)
+      {
+          debugWarning("User %s is considered to have an expired account " +
+               "because an error occurred while attempting to make " +
+               "the determination: %s.",
               userDNString, stackTraceToSingleLineString(e));
-        }
-
-        isAccountExpired = ConditionResult.TRUE;
-        return true;
-      }
-    }
-
-
-    if (isAccountExpired == ConditionResult.FALSE)
-    {
-      if (debug)
-      {
-        if (debugEnabled())
-        {
-          debugInfo("Returning stored result of false for user %s",
-                    userDNString);
-        }
-      }
-
-      return false;
-    }
-    else
-    {
-      if (debug)
-      {
-        if (debugEnabled())
-        {
-          debugInfo("Returning stored result of true for user %s",
-                    userDNString);
-        }
       }
 
       return true;
     }
+
+    if (expirationTime > currentTime)
+    {
+      // The user does have an expiration time, but it hasn't arrived yet.
+      isAccountExpired = ConditionResult.FALSE;
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("The account for user %s is not expired because the " +
+              "expiration time has not yet arrived.", userDNString);
+        }
+      }
+    }
+    else if (expirationTime >= 0)
+    {
+      // The user does have an expiration time, and it is in the past.
+      isAccountExpired = ConditionResult.TRUE;
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("The account for user %s is expired because the " +
+              "expiration time in that account has passed.", userDNString);
+        }
+      }
+    }
+    else
+    {
+      // The user doesn't have an expiration time in their entry, so it
+      // can't be expired.
+      isAccountExpired = ConditionResult.FALSE;
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("The account for user %s is not expired because " +
+              "there is no expiration time in the user's entry.",
+          userDNString);
+        }
+      }
+    }
+
+    return isAccountExpired == ConditionResult.TRUE;
   }
 
 
 
   /**
    * Retrieves the set of times of failed authentication attempts for the user.
+   * If authentication failure time expiration is enabled, and there are expired
+   * times in the entry, these times are removed from the instance field and an
+   * update is provided to delete those values from the entry.
    *
-   * @return  The set of times of failed authentication attempts for the user.
+   * @return  The set of times of failed authentication attempts for the user,
+   *          which will be an empty list in the case of no valid (unexpired)
+   *          times in the entry.
    */
-  public List<Long> getAuthFailureTimes()
+  private List<Long> getAuthFailureTimes()
   {
-    if (authFailureTimes == null)
+    if (authFailureTimes != null)
     {
-      AttributeType type =
-           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
-      if (type == null)
-      {
-        type = DirectoryServer.getDefaultAttributeType(
-                                    OP_ATTR_PWPOLICY_FAILURE_TIME);
-      }
-
-      try
-      {
-        authFailureTimes = getGeneralizedTimes(type);
-
-
-        // Remove any expired failures from the list.
-        if (passwordPolicy.getLockoutFailureExpirationInterval() > 0)
-        {
-          LinkedHashSet<AttributeValue> values = null;
-
-          long expirationTime = currentTime -
-               (passwordPolicy.getLockoutFailureExpirationInterval()*1000L);
-          Iterator<Long> iterator = authFailureTimes.iterator();
-          while (iterator.hasNext())
-          {
-            long l = iterator.next();
-            if (l < expirationTime)
-            {
-              if (debug)
-              {
-                if (debugEnabled())
-                {
-                  debugInfo("Removing expired auth failure time %d for user " +
-                      "%s", l, userDNString);
-                }
-              }
-
-              iterator.remove();
-
-              if (values == null)
-              {
-                values = new LinkedHashSet<AttributeValue>();
-              }
-
-              values.add(new AttributeValue(type,
-                                            GeneralizedTimeSyntax.format(l)));
-            }
-          }
-
-          if (values != null)
-          {
-            Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
-                                        values);
-            ArrayList<Attribute> removeList = new ArrayList<Attribute>(1);
-            removeList.add(a);
-
-
-            if (authFailureTimes.isEmpty())
-            {
-              if (updateEntry)
-              {
-                userEntry.removeAttribute(type);
-              }
-            }
-            else
-            {
-              LinkedHashSet<AttributeValue> keepValues =
-                   new LinkedHashSet<AttributeValue>(authFailureTimes.size());
-              for (Long l : authFailureTimes)
-              {
-                keepValues.add(new AttributeValue(type,
-                                        GeneralizedTimeSyntax.format(l)));
-              }
-
-              ArrayList<Attribute> keepList = new ArrayList<Attribute>(1);
-              keepList.add(new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
-                                         keepValues));
-
-              if (updateEntry)
-              {
-                userEntry.putAttribute(type, keepList);
-              }
-            }
-
-
-            if (! updateEntry)
-            {
-              modifications.add(new Modification(ModificationType.DELETE, a,
-                                                 true));
-            }
-          }
-        }
-      }
-      catch (Exception e)
+      if(debug)
       {
         if (debugEnabled())
         {
-          debugCaught(DebugLogLevel.ERROR, e);
+          debugInfo("Returning stored auth failure time list of %d " +
+              "elements for user %s" +
+              authFailureTimes.size(), userDNString);
         }
+      }
 
-        if (debug)
+      return authFailureTimes;
+    }
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
+    if (type == null)
+    {
+      type = DirectoryServer.getDefaultAttributeType(
+           OP_ATTR_PWPOLICY_FAILURE_TIME);
+    }
+
+    try
+    {
+      authFailureTimes = getGeneralizedTimes(type);
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      if (debug)
+      {
+        debugWarning("Error while processing auth failure times " +
+             "for user %s: %s",
+                     userDNString, stackTraceToSingleLineString(e));
+      }
+
+      authFailureTimes = new ArrayList<Long>();
+
+      if (updateEntry)
+      {
+        userEntry.removeAttribute(type);
+      }
+      else
+      {
+        modifications.add(new Modification(ModificationType.REPLACE,
+                                           new Attribute(type), true));
+      }
+
+      return authFailureTimes;
+    }
+
+    if (authFailureTimes.isEmpty())
+    {
+      if (debug)
+      {
+       if (debugEnabled())
         {
-          debugWarning(
-              "Error while processing auth failure times " +
-                  "for user %s: %s",
-              userDNString,
-              stackTraceToSingleLineString(e));
+          debugInfo("Returning an empty auth failure time list for user %s" +
+                    " because the attribute is absent from the entry.",
+                    userDNString);
         }
+      }
 
-        authFailureTimes = new ArrayList<Long>();
+      return authFailureTimes;
+    }
 
+    // Remove any expired failures from the list.
+    if (passwordPolicy.getLockoutFailureExpirationInterval() > 0)
+    {
+      LinkedHashSet<AttributeValue> valuesToRemove = null;
+
+      long expirationTime = currentTime -
+           (passwordPolicy.getLockoutFailureExpirationInterval() * 1000L);
+      Iterator<Long> iterator = authFailureTimes.iterator();
+      while (iterator.hasNext())
+      {
+        long l = iterator.next();
+        if (l < expirationTime)
+        {
+          if (debug)
+          {
+            if (debugEnabled())
+            {
+              debugInfo("Removing expired auth failure time %d for user %s",
+                        l, userDNString);
+            }
+          }
+
+          iterator.remove();
+
+          if (valuesToRemove == null)
+          {
+            valuesToRemove = new LinkedHashSet<AttributeValue>();
+          }
+
+          valuesToRemove.add(new AttributeValue(type,
+                                              GeneralizedTimeSyntax.format(l)));
+        }
+      }
+
+      if (valuesToRemove != null)
+      {
         if (updateEntry)
         {
-          userEntry.removeAttribute(type);
+          if (authFailureTimes.isEmpty())
+          {
+            userEntry.removeAttribute(type);
+          }
+          else
+          {
+            LinkedHashSet<AttributeValue> keepValues =
+                 new LinkedHashSet<AttributeValue>(authFailureTimes.size());
+            for (Long l : authFailureTimes)
+            {
+              keepValues.add(
+                   new AttributeValue(type, GeneralizedTimeSyntax.format(l)));
+            }
+            ArrayList<Attribute> keepList = new ArrayList<Attribute>(1);
+            keepList.add(new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
+                                       keepValues));
+            userEntry.putAttribute(type, keepList);
+          }
         }
         else
         {
-          modifications.add(new Modification(ModificationType.REPLACE,
-                                             new Attribute(type), true));
+          Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
+                                      valuesToRemove);
+          modifications.add(new Modification(ModificationType.DELETE, a,
+                                             true));
         }
       }
     }
 
-
     if (debug)
     {
       if (debugEnabled())
       {
-        debugInfo("Returning auth failure time list of %d " +
-            "elements for user %s" +
+        debugInfo("Returning auth failure time list of %d elements for user %s",
             authFailureTimes.size(), userDNString);
       }
     }
@@ -1272,10 +1109,13 @@
 
   /**
    * Updates the set of authentication failure times to include the current
-   * time.
+   * time. If the number of failures reaches the policy configuration limit,
+   * lock the account.
    */
   public void updateAuthFailureTimes()
   {
+    assert passwordPolicy.getLockoutFailureCount() > 0;
+
     if (debug)
     {
       if (debugEnabled())
@@ -1287,6 +1127,7 @@
 
 
     List<Long> failureTimes = getAuthFailureTimes();
+    // Note: failureTimes == this.authFailureTimes
     long highestFailureTime = -1;
     for (Long l : failureTimes)
     {
@@ -1337,15 +1178,30 @@
     {
       modifications.add(new Modification(ModificationType.ADD, addAttr, true));
     }
+
+    // Now check to see if there have been sufficient failures to lock the
+    // account.
+    if (passwordPolicy.getLockoutFailureCount() <= failureTimes.size())
+    {
+      setFailureLockedTime(highestFailureTime);
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("Locking user account %s due to too many failures.",
+                    userDNString);
+        }
+      }
+    }
   }
 
 
 
   /**
    * Updates the user entry to remove any record of previous authentication
-   * failures.
+   * failure times.
    */
-  public void clearAuthFailureTimes()
+  private void clearAuthFailureTimes()
   {
     if (debug)
     {
@@ -1361,7 +1217,8 @@
     {
       return;
     }
-    failureTimes.clear();
+
+    failureTimes.clear(); // Note: failureTimes == this.authFailureTimes
 
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
@@ -1383,206 +1240,68 @@
   }
 
 
-
   /**
-   * Indicates whether the associated user should be considered locked out as a
-   * result of too many authentication failures.
+   * Retrieves the time of an authentication failure lockout for the user.
    *
-   * @return  <CODE>true</CODE> if the user is currently locked out due to too
-   *          many authentication failures, or <CODE>false</CODE> if not.
+   * @return  The time of an authentication failure lockout for the user, or -1
+   *          if no such time is present in the entry.
    */
-  public boolean lockedDueToFailures()
+  private long getFailureLockedTime()
   {
-    int maxFailures = passwordPolicy.getLockoutFailureCount();
-    if (maxFailures <= 0)
+    if (failureLockedTime != Long.MIN_VALUE)
     {
-      if (debug)
-      {
-        if (debugEnabled())
-        {
-          debugInfo("Returning false for user %s because lockout due to " +
-              "failures is not enabled.", userDNString);
-        }
-      }
-
-      return false;
+      return failureLockedTime;
     }
 
-
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
     if (type == null)
     {
       type = DirectoryServer.getDefaultAttributeType(
-                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
+           OP_ATTR_PWPOLICY_LOCKED_TIME);
     }
 
-    // Get the locked time from the user's entry.  If it's not there, then the
-    // account is not locked.
-    if (failureLockedTime == Long.MIN_VALUE)
+    try
     {
-      try
-      {
-        failureLockedTime = getGeneralizedTime(type);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        if (debug)
-        {
-          debugWarning(
-              "Returning true for user %s because an error occurred: %s",
-              userDNString, stackTraceToSingleLineString(e));
-        }
-
-        return true;
-      }
+      failureLockedTime = getGeneralizedTime(type);
     }
-
-    if (failureLockedTime <= 0)
-    {
-      // There is no failure locked time, but that doesn't mean that the
-      // account isn't locked anyway due to the maximum number of failures
-      // (which may happen in certain cases due to synchronization latency).
-      List<Long> failureTimes = getAuthFailureTimes();
-      if ((failureTimes != null) && (failureTimes.size() >= maxFailures))
-      {
-        // The account isn't locked but should be, so do so now.
-        lockDueToFailures();
-
-        if (debug)
-        {
-          if (debugEnabled())
-          {
-            debugInfo("Setting the lock for user " + userDNString +
-                " because there were enough preexisting failures even " +
-                "though there was no account locked time.");
-          }
-        }
-
-        return true;
-      }
-
-
-      if (debug)
-      {
-        if (debugEnabled())
-        {
-          debugInfo("Returning false for user  because there is no locked " +
-              "time.", userDNString);
-        }
-      }
-
-      return false;
-    }
-
-    // There is a failure locked time, but it may be expired.  See if that's the
-    // case.
-    if (passwordPolicy.getLockoutDuration() > 0)
-    {
-      long unlockTime = failureLockedTime +
-          (1000L * passwordPolicy.getLockoutDuration());
-      if (unlockTime > currentTime)
-      {
-        if (debug)
-        {
-          if (debugEnabled())
-          {
-            debugInfo("Returning true for user %s because there is a locked " +
-                "time and the lockout duration has not been reached.",
-                      userDNString);
-          }
-
-          secondsUntilUnlock = (int) (unlockTime - currentTime);
-        }
-
-        return true;
-      }
-      else
-      {
-        if (updateEntry)
-        {
-          userEntry.removeAttribute(type);
-        }
-        else
-        {
-          modifications.add(new Modification(ModificationType.REPLACE,
-                                             new Attribute(type), true));
-        }
-
-        if (debug)
-        {
-          if (debugEnabled())
-          {
-            debugInfo("Returning false for user %s " +
-                "because the existing lockout has expired.", userDNString);
-          }
-        }
-
-        return false;
-      }
-    }
-    else
-    {
-      if (debug)
-      {
-        if (debugEnabled())
-        {
-          debugInfo("Returning true for user %s " +
-              "because there is a locked time and no lockout duration.",
-                    userDNString);
-        }
-      }
-
-      return true;
-    }
-  }
-
-
-
-  /**
-   * Retrieves the length of time in seconds until the user's account is
-   * automatically unlocked.  This should only be called after calling
-   * <CODE>lockedDueToFailures</CODE>.
-   *
-   * @return  The length of time in seconds until the user's account is
-   *          automatically unlocked, or -1 if the account is not locked or the
-   *          lockout requires administrative action to clear.
-   */
-  public int getSecondsUntilUnlock()
-  {
-    if (secondsUntilUnlock < 0)
-    {
-      return -1;
-    }
-    else
-    {
-      return secondsUntilUnlock;
-    }
-  }
-
-
-
-  /**
-   * Updates the user account to indicate that it has been locked due to too
-   * many authentication failures.
-   */
-  public void lockDueToFailures()
-  {
-    if (debug)
+    catch (Exception e)
     {
       if (debugEnabled())
       {
-        debugInfo("Locking user account %s due to too many failures.",
-                  userDNString);
+        debugCaught(DebugLogLevel.ERROR, e);
       }
+
+      failureLockedTime = currentTime;
+      if (debug)
+      {
+        debugWarning("Returning current time for user %s because an error " +
+             "occurred: %s",
+                     userDNString, stackTraceToSingleLineString(e));
+      }
+
+      return failureLockedTime;
     }
 
-    failureLockedTime = currentTime;
+    // An expired locked time is handled in lockedDueToFailures.
+    return failureLockedTime;
+  }
+
+
+
+  /**
+    Sets the failure lockout attribute in the entry to the requested time.
+
+    @param time  The time to which to set the entry's failure lockout attribute.
+   */
+  private void setFailureLockedTime(final long time)
+  {
+    if (time == getFailureLockedTime())
+    {
+      return;
+    }
+
+    failureLockedTime = time;
 
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
@@ -1596,11 +1315,11 @@
     values.add(new AttributeValue(type,
                         GeneralizedTimeSyntax.format(failureLockedTime)));
     Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_LOCKED_TIME, values);
-    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-    attrList.add(a);
 
     if (updateEntry)
     {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
       userEntry.putAttribute(type, attrList);
     }
     else
@@ -1612,24 +1331,26 @@
 
 
   /**
-   * Updates the user account to remove any record of a previous lockout due to
-   * failed authentications.
+   * Updates the user entry to remove any record of previous authentication
+   * failure lockout.
    */
-  public void clearFailureLockout()
+  private void clearFailureLockedTime()
   {
     if (debug)
     {
       if (debugEnabled())
       {
-        debugInfo("Clearing lockout failures for user %s", userDNString);
+        debugInfo("Clearing failure lockout time for user %s.", userDNString);
       }
     }
 
-    if (! lockedDueToFailures())
+    if (-1L == getFailureLockedTime())
     {
       return;
     }
 
+    failureLockedTime = -1L;
+
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
     if (type == null)
@@ -1652,6 +1373,174 @@
 
 
   /**
+   * Indicates whether the associated user should be considered locked out as a
+   * result of too many authentication failures. In the case of an expired
+   * lock-out, this routine produces the update to clear the lock-out attribute
+   * and the authentication failure timestamps.
+   * In case the failure lockout time is absent from the entry, but sufficient
+   * authentication failure timestamps are present in the entry, this routine
+   * produces the update to set the lock-out attribute.
+   *
+   * @return  <CODE>true</CODE> if the user is currently locked out due to too
+   *          many authentication failures, or <CODE>false</CODE> if not.
+   */
+  public boolean lockedDueToFailures()
+  {
+    // FIXME: Introduce a state field to cache the computed value of this
+    // method. Note that only a cached "locked" status can be returned due to
+    // the possibility of intervening updates to this.failureLockedTime by
+    // updateAuthFailureTimes.
+
+    // Check if the feature is enabled in the policy.
+    final int maxFailures = passwordPolicy.getLockoutFailureCount();
+    if (maxFailures <= 0)
+    {
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("Returning false for user %s because lockout due to " +
+               "failures is not enabled.", userDNString);
+        }
+      }
+
+      return false;
+    }
+
+    // Get the locked time from the user's entry. If it is present and not
+    // expired, the account is locked. If it is absent, the failure timestamps
+    // must be checked, since failure timestamps sufficient to lock the
+    // account could be produced across the synchronization topology within the
+    // synchronization latency. Also, note that IETF
+    // draft-behera-ldap-password-policy-09 specifies "19700101000000Z" as
+    // the value to be set under a "locked until reset" regime; however, this
+    // implementation accepts the value as a locked entry, but observes the
+    // lockout expiration policy for all values including this one.
+    // FIXME: This "getter" is unusual in that it might produce an update to the
+    // entry in two cases. Does it make sense to factor the methods so that,
+    // e.g., an expired lockout is reported, and clearing the lockout is left to
+    // the caller?
+    if (getFailureLockedTime() < 0L)
+    {
+      // There was no locked time present in the entry; however, sufficient
+      // failure times might have accumulated to trigger a lockout.
+      if (getAuthFailureTimes().size() < maxFailures)
+      {
+        if (debug)
+        {
+          if (debugEnabled())
+          {
+            debugInfo("Returning false for user %s because there is no " +
+                 "locked time.", userDNString);
+          }
+        }
+
+        return false;
+      }
+
+      // The account isn't locked but should be, so do so now.
+      setFailureLockedTime(currentTime);// FIXME: set to max(failureTimes)?
+
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("Locking user %s because there were enough existing " +
+               "failures even though there was no account locked time.",
+                    userDNString);
+        }
+      }
+      // Fall through...
+    }
+
+    // There is a failure locked time, but it may be expired.
+    if (passwordPolicy.getLockoutDuration() > 0)
+    {
+      final long unlockTime = getFailureLockedTime() +
+           (1000L * passwordPolicy.getLockoutDuration());
+      if (unlockTime > currentTime)
+      {
+        secondsUntilUnlock = (int) (unlockTime - currentTime);
+
+        if (debug)
+        {
+          if (debugEnabled())
+          {
+            debugInfo("Returning true for user %s because there is a locked " +
+                 "time and the lockout duration has not been reached.",
+                      userDNString);
+          }
+        }
+
+        return true;
+      }
+
+      // The lockout in the entry has expired...
+      clearFailureLockout();
+
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("Returning false for user %s " +
+               "because the existing lockout has expired.", userDNString);
+        }
+      }
+
+      assert -1L == getFailureLockedTime();
+      return false;
+    }
+
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        debugInfo("Returning true for user %s " +
+             "because there is a locked time and no lockout duration.",
+                  userDNString);
+      }
+    }
+
+    assert -1L <= getFailureLockedTime();
+    return true;
+  }
+
+
+
+  /**
+   * Retrieves the length of time in seconds until the user's account is
+   * automatically unlocked.  This should only be called after calling
+   * <CODE>lockedDueToFailures</CODE>.
+   *
+   * @return  The length of time in seconds until the user's account is
+   *          automatically unlocked, or -1 if the account is not locked or the
+   *          lockout requires administrative action to clear.
+   */
+  public int getSecondsUntilUnlock()
+  {
+    // secondsUntilUnlock is only set when failureLockedTime is present and
+    // PasswordPolicy.getLockoutDuration is enabled; hence it is not
+    // unreasonable to find secondsUntilUnlock uninitialized.
+    assert failureLockedTime != Long.MIN_VALUE;
+
+    return (secondsUntilUnlock < 0) ? -1 : secondsUntilUnlock;
+  }
+
+
+
+  /**
+   * Updates the user account to remove any record of a previous lockout due to
+   * failed authentications.
+   */
+  public void clearFailureLockout()
+  {
+    clearAuthFailureTimes();
+    clearFailureLockedTime();
+  }
+
+
+
+  /**
    * Retrieves the time that the user last authenticated to the Directory
    * Server.
    *
@@ -1660,139 +1549,125 @@
    */
   public long getLastLoginTime()
   {
-    if (lastLoginTime == Long.MIN_VALUE)
+    if (lastLoginTime != Long.MIN_VALUE)
     {
-      AttributeType type   = passwordPolicy.getLastLoginTimeAttribute();
-      String        format = passwordPolicy.getLastLoginTimeFormat();
-
-      if ((type == null) || (format == null))
+      if (debug)
       {
-        if (debug)
+        if (debugEnabled())
         {
-          if (debugEnabled())
-          {
-            debugInfo("Returning -1 for user %s because no last " +
-                "login time will be maintained.", userDNString);
-          }
+          debugInfo("Returning stored last login time of %d for user %s.",
+                    lastLoginTime, userDNString);
         }
-
-        lastLoginTime = -1;
-        return lastLoginTime;
       }
 
-      List<Attribute> attrList = userEntry.getAttribute(type);
-      if ((attrList == null) || attrList.isEmpty())
-      {
-        if (debug)
-        {
-          if (debugEnabled())
-          {
-            debugInfo("Returning -1 for user %s because no last " +
-                "login time value exists.", userDNString);
-          }
-        }
+      return lastLoginTime;
+    }
 
-        lastLoginTime = -1;
-        return lastLoginTime;
+    // The policy configuration must be checked since the entry cannot be
+    // evaluated without both an attribute name and timestamp format.
+    AttributeType type   = passwordPolicy.getLastLoginTimeAttribute();
+    String        format = passwordPolicy.getLastLoginTimeFormat();
+
+    if ((type == null) || (format == null))
+    {
+      lastLoginTime = -1;
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("Returning -1 for user %s because no last login time " +
+               "will be maintained.", userDNString);
+        }
       }
 
+      return lastLoginTime;
+    }
+
+    lastLoginTime = -1;
+    List<Attribute> attrList = userEntry.getAttribute(type);
+
+    if (attrList != null)
+    {
       for (Attribute a : attrList)
       {
-        for (AttributeValue v : a.getValues())
+        if (a.getValues().isEmpty()) continue;
+
+        String valueString = a.getValues().iterator().next().getStringValue();
+
+        try
         {
-          String valueString = v.getStringValue();
-          SimpleDateFormat dateFormat;
+          SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+          lastLoginTime = dateFormat.parse(valueString).getTime();
 
-          try
-          {
-            dateFormat    = new SimpleDateFormat(format);
-            lastLoginTime = dateFormat.parse(valueString).getTime();
-
-            if (debug)
-            {
-              if (debugEnabled())
-              {
-                debugInfo("Returning last login time of %s for user " +
-                    "%s decoded using current last login " +
-                    "time format.", lastLoginTime, userDNString);
-              }
-            }
-
-            return lastLoginTime;
-          }
-          catch (Exception e)
+          if (debug)
           {
             if (debugEnabled())
             {
-              debugCaught(DebugLogLevel.ERROR, e);
-            }
-
-            // This could mean that the last login time was encoded using a
-            // previous format.
-            for (String f : passwordPolicy.getPreviousLastLoginTimeFormats())
-            {
-              try
-              {
-                dateFormat = new SimpleDateFormat(f);
-                lastLoginTime = dateFormat.parse(valueString).getTime();
-
-                if (debug)
-                {
-                  if (debugEnabled())
-                  {
-                    debugInfo("Returning last login time of %s for " +
-                        "user %s decoded using previous " +
-                        "last login time format of %s",
-                              lastLoginTime, userDNString, f);
-                  }
-                }
-
-                return lastLoginTime;
+              debugInfo("Returning last login time of %d for user %s" +
+                   "decoded using current last login time format.",
+                        lastLoginTime, userDNString);
               }
-              catch (Exception e2)
+          }
+
+          return lastLoginTime;
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          // This could mean that the last login time was encoded using a
+          // previous format.
+          for (String f : passwordPolicy.getPreviousLastLoginTimeFormats())
+          {
+            try
+            {
+              SimpleDateFormat dateFormat = new SimpleDateFormat(f);
+              lastLoginTime = dateFormat.parse(valueString).getTime();
+
+              if (debug)
               {
                 if (debugEnabled())
                 {
-                  debugCaught(DebugLogLevel.ERROR, e2);
+                  debugInfo("Returning last login time of %d for user %s" +
+                       "decoded using previous last login time format of %s.",
+                            lastLoginTime, userDNString, f);
                 }
               }
+
+              return lastLoginTime;
             }
-
-
-            if (debug)
+            catch (Exception e2)
             {
-              debugWarning(
-                  "Returning -1 for user %s because the " +
-                      "last login time value %s could not " +
-                      "be parsed using any known format.",
-                  userDNString, valueString);
+              if (debugEnabled())
+              {
+                debugCaught(DebugLogLevel.ERROR, e);
+              }
             }
-
-            lastLoginTime = -1;
-            return lastLoginTime;
           }
+
+          assert lastLoginTime == -1;
+          if (debug)
+          {
+              debugWarning("Returning -1 for user %s because the last login " +
+                   "time value %s could not be parsed using any known format.",
+                           userDNString, valueString);
+          }
+
+          return lastLoginTime;
         }
       }
-
-
-      // We shouldn't get here.
-      if (debug)
-      {
-        debugWarning(
-            "Returning -1 for user %s because even though " +
-                         "there appears to be a last login time " +
-                         "value we couldn't decipher it.",
-                     userDNString);
-      }
-      return -1;
     }
 
+    assert lastLoginTime == -1;
     if (debug)
     {
       if (debugEnabled())
       {
-        debugInfo("Returning previously calculated last login time " +
-            "of %s for user %s", lastLoginTime, userDNString);
+        debugInfo("Returning %d for user %s because no last " +
+            "login time value exists.", lastLoginTime, userDNString);
       }
     }
 
@@ -1829,10 +1704,9 @@
 
       if (debug)
       {
-        debugWarning(
-            "Unable to set last login time for user %s because an " +
-                "error occurred: %s",
-            userDNString, stackTraceToSingleLineString(e));
+        debugWarning("Unable to set last login time for user %s because an " +
+             "error occurred: %s",
+                     userDNString, stackTraceToSingleLineString(e));
       }
 
       return;
@@ -1859,11 +1733,11 @@
     values.add(new AttributeValue(type, timestamp));
 
     Attribute a = new Attribute(type, type.getNameOrOID(), values);
-    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-    attrList.add(a);
 
     if (updateEntry)
     {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
       userEntry.putAttribute(type, attrList);
     }
     else
@@ -1892,264 +1766,207 @@
    */
   public boolean lockedDueToIdleInterval()
   {
-    if ((isIdleLocked == null) || (isIdleLocked == ConditionResult.UNDEFINED))
+    if (isIdleLocked != ConditionResult.UNDEFINED)
     {
-      if (passwordPolicy.getIdleLockoutInterval() <= 0)
+      if (debug)
       {
-        if (debug)
+        if (debugEnabled())
         {
-          if (debugEnabled())
-          {
-            debugInfo("Returning false for user %s because no idle lockout " +
-                "interval is defined.", userDNString);
-          }
+          debugInfo("Returning stored result of %b for user %s",
+                    (isIdleLocked == ConditionResult.TRUE), userDNString);
         }
-
-        isIdleLocked = ConditionResult.FALSE;
-        return false;
       }
 
-      long lockTime = currentTime -
-          (passwordPolicy.getIdleLockoutInterval() * 1000L);
-      long lastLoginTime = getLastLoginTime();
-      if (lastLoginTime > 0)
+      return isIdleLocked == ConditionResult.TRUE;
+    }
+
+    // Return immediately if this feature is disabled, since the feature is not
+    // responsible for any state attribute in the entry.
+    if (passwordPolicy.getIdleLockoutInterval() <= 0)
+    {
+      isIdleLocked = ConditionResult.FALSE;
+
+      if (debug)
       {
-        if (lastLoginTime > lockTime)
+        if (debugEnabled())
         {
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("Returning false for user %s because the last login " +
-                  "time is in an acceptable window.", userDNString);
-            }
-          }
-
-          isIdleLocked = ConditionResult.FALSE;
-          return false;
+          debugInfo("Returning false for user %s because no idle lockout " +
+              "interval is defined.", userDNString);
         }
-        else
-        {
-          if (passwordChangedTime > lockTime)
-          {
-            if (debug)
-            {
-              if (debugEnabled())
-              {
-                debugInfo("Returning false for user  because the password " +
-                    "changed time is in an acceptable window.", userDNString);
-              }
-            }
+      }
 
-            isIdleLocked = ConditionResult.FALSE;
-            return false;
+      return false;
+    }
+
+    long lockTime = currentTime -
+                         (1000L * passwordPolicy.getIdleLockoutInterval());
+    if(lockTime < 0) lockTime = 0;
+
+    long lastLoginTime = getLastLoginTime();
+    if (lastLoginTime > lockTime || passwordChangedTime > lockTime)
+    {
+      isIdleLocked = ConditionResult.FALSE;
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          StringBuilder reason = new StringBuilder();
+          if(lastLoginTime > lockTime)
+          {
+            reason.append("the last login time is in an acceptable window");
           }
           else
           {
-            if (debug)
+            if(lastLoginTime < 0)
             {
-              if (debugEnabled())
-              {
-                debugInfo("Returning true for user because neither last " +
-                    "login time nor password changed time are in an " +
-                    "acceptable window.", userDNString);
-              }
+              reason.append("there is no last login time, but ");
             }
-
-            isIdleLocked = ConditionResult.TRUE;
-            return true;
+            reason.append(
+                 "the password changed time is in an acceptable window");
           }
+          debugInfo("Returning false for user %s because %s.",
+                    userDNString, reason.toString());
         }
       }
-      else
-      {
-        if (passwordChangedTime < lockTime)
-        {
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("Returning true for user %s because there is no last " +
-                  "login time and the password changed time is not in " +
-                  "an acceptable window.", userDNString);
-            }
-          }
-
-          isIdleLocked = ConditionResult.TRUE;
-          return true;
-        }
-        else
-        {
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("Returning false for user %s because there is no " +
-                  "last login time but the password changed time is in an " +
-                  "acceptable window.", userDNString);
-            }
-          }
-
-          isIdleLocked = ConditionResult.FALSE;
-          return false;
-        }
-      }
-    }
-
-
-    if (isIdleLocked == ConditionResult.TRUE)
-    {
-      if (debug)
-      {
-        if (debugEnabled())
-        {
-          debugInfo("Returning stored result of true for user %s",
-                    userDNString);
-        }
-      }
-
-      return true;
     }
     else
     {
+      isIdleLocked = ConditionResult.TRUE;
       if (debug)
       {
         if (debugEnabled())
         {
-          debugInfo("Returning stored result of false for user %s",
-                    userDNString);
+          String reason = (lastLoginTime < 0)
+             ? "there is no last login time and the password " +
+                  "changed time is not in an acceptable window"
+             : "neither last login time nor password " +
+                  "changed time are in an acceptable window";
+          debugInfo("Returning true for user %s because %s.",
+                    userDNString, reason);
         }
       }
-
-      return false;
     }
+
+    return isIdleLocked == ConditionResult.TRUE;
   }
 
 
 
-  /**
-   * Indicates whether the user's password must be changed before any other
-   * operation can be performed.
-   *
-   * @return  <CODE>true</CODE> if the user's password must be changed before
-   *          any other operation can be performed.
-   */
+/**
+* Indicates whether the user's password must be changed before any other
+* operation can be performed.
+*
+* @return  <CODE>true</CODE> if the user's password must be changed before
+*          any other operation can be performed.
+*/
   public boolean mustChangePassword()
   {
-    // If the password policy doesn't use force change on add or force change on
-    // reset, or if it forbits the user from changing their password, then this
-    // must return false.
-    if (! passwordPolicy.allowUserPasswordChanges())
-    {
-      return false;
-    }
-    else if (! (passwordPolicy.forceChangeOnAdd() ||
-                passwordPolicy.forceChangeOnReset()))
-    {
-      return false;
-    }
-
-    if ((mustChangePassword == null) ||
-        (mustChangePassword == ConditionResult.UNDEFINED))
-    {
-      AttributeType type =
-           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
-      if (type == null)
-      {
-        type = DirectoryServer.getDefaultAttributeType(
-                                    OP_ATTR_PWPOLICY_RESET_REQUIRED);
-      }
-
-      try
-      {
-        boolean resetRequired = getBoolean(type, false);
-        if (resetRequired)
-        {
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("Returning true for user %", userDNString);
-            }
-          }
-
-          mustChangePassword = ConditionResult.TRUE;
-          return true;
-        }
-        else
-        {
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("Returning false for user %s", userDNString);
-            }
-          }
-
-          mustChangePassword = ConditionResult.FALSE;
-          return false;
-        }
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        if (debug)
-        {
-          if (debugEnabled())
-          {
-            debugInfo("Returning true for user %s because an unexpected " +
-                "error occurred: %s",
-                      userDNString, stackTraceToSingleLineString(e));
-          }
-        }
-
-        mustChangePassword = ConditionResult.TRUE;
-        return true;
-      }
-    }
-
-
-    if (mustChangePassword == ConditionResult.TRUE)
+    if(mustChangePassword != ConditionResult.UNDEFINED)
     {
       if (debug)
       {
         if (debugEnabled())
         {
-          debugInfo("Returning stored result of true for user %s",
-                    userDNString);
+          debugInfo("Returning stored result of %b for user %s.",
+                    (mustChangePassword == ConditionResult.TRUE), userDNString);
         }
       }
 
+      return mustChangePassword == ConditionResult.TRUE;
+    }
+
+    // If the password policy doesn't use force change on add or force change on
+    // reset, or if it forbids the user from changing his password, then return
+    // false.
+    // FIXME: the only getter responsible for a state attribute (pwdReset) that
+    // considers the policy before checking the entry for the presence of the
+    // attribute.
+    if (! (passwordPolicy.allowUserPasswordChanges()
+           && (passwordPolicy.forceChangeOnAdd()
+               || passwordPolicy.forceChangeOnReset())))
+    {
+      mustChangePassword = ConditionResult.FALSE;
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          debugInfo("Returning false for user %s because neither force " +
+               "change on add nor force change on reset is enabled, " +
+               "or users are not allowed to self-modify passwords.",
+                    userDNString);
+
+        }
+      }
+
+      return false;
+    }
+
+    AttributeType type =
+           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
+    if (type == null)
+    {
+      type = DirectoryServer.getDefaultAttributeType(
+           OP_ATTR_PWPOLICY_RESET_REQUIRED);
+    }
+
+    try
+    {
+      mustChangePassword = getBoolean(type);
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      mustChangePassword = ConditionResult.TRUE;
+      if (debug)
+      {
+        debugWarning("Returning true for user %s because an error occurred: %s",
+                     userDNString, stackTraceToSingleLineString(e));
+      }
+
       return true;
     }
-    else
+
+    if(mustChangePassword == ConditionResult.UNDEFINED)
     {
-      if (debug)
+      mustChangePassword = ConditionResult.FALSE;
+      if(debug)
       {
         if (debugEnabled())
         {
-          debugInfo("Returning stored result of false for user %s",
-                    userDNString);
+          debugInfo("Returning %b for user since the attribute \"%s\"" +
+               " is not present in the entry.",
+                    false, userDNString, OP_ATTR_PWPOLICY_RESET_REQUIRED);
         }
       }
 
       return false;
     }
+
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        debugInfo("Returning %b for user %s.",
+                  (mustChangePassword == ConditionResult.TRUE), userDNString);
+      }
+    }
+
+    return mustChangePassword == ConditionResult.TRUE;
   }
 
 
 
-  /**
-   * Updates the user entry to indicate whether the user's password must be
-   * changed.
-   *
-   * @param  mustChangePassword  Indicates whether the user's password must be
-   *                             changed.
-   */
+/**
+* Updates the user entry to indicate whether the user's password must be
+* changed.
+*
+* @param  mustChangePassword  Indicates whether the user's password must be
+*                             changed.
+*/
   public void setMustChangePassword(boolean mustChangePassword)
   {
     if (debug)
@@ -2161,8 +1978,8 @@
       }
     }
 
-    if (mustChangePassword ==
-            (this.mustChangePassword == ConditionResult.TRUE)){
+    if (mustChangePassword == mustChangePassword())
+    {
       return;  // requested state matches current state
     }
 
@@ -2198,6 +2015,7 @@
     }
     else
     {
+      // erase
       if (updateEntry)
       {
         userEntry.removeAttribute(type);
@@ -2222,6 +2040,8 @@
    */
   public boolean lockedDueToMaximumResetAge()
   {
+    // This feature is reponsible for neither a state field nor an entry state
+    // attribute.
     if (passwordPolicy.getMaximumPasswordResetAge() <= 0)
     {
       if (debug)
@@ -2229,14 +2049,14 @@
         if (debugEnabled())
         {
           debugInfo("Returning false for user %s because there is no maximum " +
-              "reset age .", userDNString);
+              "reset age.", userDNString);
         }
       }
 
       return false;
     }
 
-    if (!mustChangePassword())
+    if (! mustChangePassword())
     {
       if (debug)
       {
@@ -2448,17 +2268,15 @@
       }
     }
 
-
     if (debug)
     {
       if (debugEnabled())
       {
-        debugInfo("Returning password expiration time of %s for user " +
-            "%s", expirationTime, userDNString);
+        debugInfo("Returning password expiration time of %d for user %s.",
+                  expirationTime, userDNString);
       }
     }
 
-    secondsUntilExpiration = (int) (expirationTime - currentTime);
     return expirationTime;
   }
 
@@ -2478,14 +2296,7 @@
       getPasswordExpirationTime();
     }
 
-    if (isPasswordExpired == ConditionResult.TRUE)
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
+    return isPasswordExpired == ConditionResult.TRUE;
   }
 
 
@@ -2500,6 +2311,8 @@
    */
   public boolean isWithinMinimumAge()
   {
+    // This feature is reponsible for neither a state field nor entry state
+    // attribute.
     int minAge = passwordPolicy.getMinimumPasswordAge();
     if (minAge <= 0)
     {
@@ -2546,7 +2359,10 @@
       // The user is within the minimum age.
       if (debug)
       {
-        debugWarning("Returning true.");
+        if (debugEnabled())
+        {
+          debugInfo("Returning true.");
+        }
       }
 
       return true;
@@ -2575,14 +2391,7 @@
       getPasswordExpirationTime();
     }
 
-    if (mayUseGraceLogin == ConditionResult.TRUE)
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
+    return mayUseGraceLogin == ConditionResult.TRUE;
   }
 
 
@@ -2602,14 +2411,7 @@
       getPasswordExpirationTime();
     }
 
-    if (shouldWarn == ConditionResult.TRUE)
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
+    return shouldWarn == ConditionResult.TRUE;
   }
 
 
@@ -2629,14 +2431,7 @@
       getPasswordExpirationTime();
     }
 
-    if (isFirstWarning == ConditionResult.TRUE)
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
+    return isFirstWarning == ConditionResult.TRUE;
   }
 
 
@@ -2677,41 +2472,50 @@
    */
   public long getRequiredChangeTime()
   {
-    if (requiredChangeTime == Long.MIN_VALUE)
+    if (requiredChangeTime != Long.MIN_VALUE)
     {
-      AttributeType type = DirectoryServer.getAttributeType(
-                                OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME,
-                                true);
-      try
-      {
-        requiredChangeTime = getGeneralizedTime(type);
-      }
-      catch (Exception e)
+      if (debug)
       {
         if (debugEnabled())
         {
-          debugCaught(DebugLogLevel.ERROR, e);
+          debugInfo("Returning stored required change time of %d for user %s",
+                    requiredChangeTime, userDNString);
         }
-
-        if (debug)
-        {
-          debugWarning(
-              "An error occurred while attempting to " +
-                  "determine the required change time for " +
-                  "user %s: %s",
-              userDNString, stackTraceToSingleLineString(e));
-        }
-
-        requiredChangeTime = -1;
       }
+
+      return requiredChangeTime;
     }
 
+    AttributeType type = DirectoryServer.getAttributeType(
+                              OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
+
+    try
+    {
+      requiredChangeTime = getGeneralizedTime(type);
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      requiredChangeTime = -1;
+      if (debug)
+      {
+        debugWarning("Returning %d for user %s because an error occurred: %s",
+                     requiredChangeTime, userDNString,
+                     stackTraceToSingleLineString(e));
+      }
+
+      return requiredChangeTime;
+    }
 
     if (debug)
     {
       if (debugEnabled())
       {
-        debugInfo("Returning required change time of %s for user %s",
+        debugInfo("Returning required change time of %d for user %s",
                   requiredChangeTime, userDNString);
       }
     }
@@ -2735,29 +2539,26 @@
       }
     }
 
-
-    long reqChangeTime = getRequiredChangeTime();
-    if (reqChangeTime != passwordPolicy.getRequireChangeByTime())
+    long requiredChangeByTimePolicy = passwordPolicy.getRequireChangeByTime();
+    if (getRequiredChangeTime() != requiredChangeByTimePolicy)
     {
-      reqChangeTime = passwordPolicy.getRequireChangeByTime();
-      requiredChangeTime = reqChangeTime;
-
       AttributeType type = DirectoryServer.getAttributeType(
-                                OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME,
-                                true);
+                               OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
+
       LinkedHashSet<AttributeValue> values =
            new LinkedHashSet<AttributeValue>(1);
-      String timeValue = GeneralizedTimeSyntax.format(passwordChangedTime);
+      String timeValue =
+           GeneralizedTimeSyntax.format(requiredChangeByTimePolicy);
       values.add(new AttributeValue(type, timeValue));
 
       Attribute a = new Attribute(type,
                                   OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME,
                                   values);
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
 
       if (updateEntry)
       {
+        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+        attrList.add(a);
         userEntry.putAttribute(type, attrList);
       }
       else
@@ -2795,10 +2596,8 @@
 
         if (debug)
         {
-          debugWarning(
-              "Unable to decode the warned time for user " +
-                  "%s: %s",
-              userDNString, stackTraceToSingleLineString(e));
+          debugWarning("Unable to decode the warned time for user %s: %s",
+                       userDNString, stackTraceToSingleLineString(e));
         }
 
         warnedTime = -1;
@@ -2848,11 +2647,11 @@
     values.add(GeneralizedTimeSyntax.createGeneralizedTimeValue(currentTime));
 
     Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_WARNED_TIME, values);
-    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-    attrList.add(a);
 
     if (updateEntry)
     {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
       userEntry.putAttribute(type, attrList);
     }
     else
@@ -2876,6 +2675,20 @@
    */
   public void clearWarnedTime()
   {
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        debugInfo("Clearing warned time for user %s", userDNString);
+      }
+    }
+
+    if (getWarnedTime() < 0)
+    {
+      return;
+    }
+    warnedTime = -1;
+
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
     if (updateEntry)
@@ -2931,10 +2744,9 @@
 
         if (debug)
         {
-          debugWarning(
-              "Error while processing grace login times " +
-                  "for user %s: %s",
-              userDNString, stackTraceToSingleLineString(e));
+          debugWarning("Error while processing grace login times " +
+               "for user %s: %s",
+                       userDNString, stackTraceToSingleLineString(e));
         }
 
         graceLoginTimes = new ArrayList<Long>();
@@ -3015,7 +2827,7 @@
     {
       highestGraceTime = currentTime;
     }
-    graceTimes.add(highestGraceTime);
+    graceTimes.add(highestGraceTime); // graceTimes == this.graceLoginTimes
 
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
@@ -3025,31 +2837,31 @@
                                   OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
     }
 
-    LinkedHashSet<AttributeValue> values =
-           new LinkedHashSet<AttributeValue>(graceTimes.size());
-    for (Long l : graceTimes)
-    {
-      values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l)));
-    }
-
-    Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME,
-                                values);
-    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-    attrList.add(a);
-
-    LinkedHashSet<AttributeValue> addValues =
-         new LinkedHashSet<AttributeValue>(1);
-    addValues.add(new AttributeValue(type,
-                           GeneralizedTimeSyntax.format(highestGraceTime)));
-    Attribute addAttr = new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME,
-                                      addValues);
-
     if (updateEntry)
     {
+      LinkedHashSet<AttributeValue> values =
+             new LinkedHashSet<AttributeValue>(graceTimes.size());
+      for (Long l : graceTimes)
+      {
+        values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l)));
+      }
+
+      Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME,
+                                  values);
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
+
       userEntry.putAttribute(type, attrList);
     }
     else
     {
+      LinkedHashSet<AttributeValue> addValues =
+           new LinkedHashSet<AttributeValue>(1);
+      addValues.add(new AttributeValue(type,
+                             GeneralizedTimeSyntax.format(highestGraceTime)));
+      Attribute addAttr = new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME,
+                                        addValues);
+
       modifications.add(new Modification(ModificationType.ADD, addAttr, true));
     }
   }
@@ -3074,7 +2886,7 @@
     {
       return;
     }
-    graceTimes.clear();
+    graceTimes.clear(); // graceTimes == this.graceLoginTimes
 
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
@@ -3109,107 +2921,75 @@
 
     List<Attribute> attrList =
          userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
-    if (attrList != null)
+
+    if (attrList == null)
     {
-      if (passwordPolicy.usesAuthPasswordSyntax())
+      return clearPasswords;
+    }
+
+    for (Attribute a : attrList)
+    {
+      boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax();
+
+      for (AttributeValue v : a.getValues())
       {
-        for (Attribute a : attrList)
+        try
         {
-          for (AttributeValue v : a.getValues())
+          StringBuilder[] pwComponents;
+          if (usesAuthPasswordSyntax)
           {
-            try
+            pwComponents =
+                 AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
+          }
+          else
+          {
+            String[] userPwComponents =
+                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
+            pwComponents = new StringBuilder[userPwComponents.length];
+            for (int i = 0; i < userPwComponents.length; ++i)
             {
-              StringBuilder[] pwComponents =
-                   AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
-              PasswordStorageScheme scheme =
-                   DirectoryServer.getAuthPasswordStorageScheme(
-                                        pwComponents[0].toString());
-              if (scheme == null)
-              {
-                if (debug)
-                {
-                  debugWarning("User entry %s contains an " +
-                                 "authPassword with scheme %s " +
-                                 "that is not defined in the " +
-                                 "server.", userDNString, pwComponents[0]);
-                }
-
-                continue;
-              }
-              else if (scheme.isReversible())
-              {
-                ByteString clearValue =
-                     scheme.getAuthPasswordPlaintextValue(
-                          pwComponents[1].toString(),
-                          pwComponents[2].toString());
-                clearPasswords.add(clearValue);
-              }
-            }
-            catch (Exception e)
-            {
-              if (debugEnabled())
-              {
-                debugCaught(DebugLogLevel.ERROR, e);
-              }
-
-              if (debug)
-              {
-                debugWarning(
-                    "Cannot get clear authPassword " +
-                        "value for user %s: %s",
-                    userDNString, e);
-              }
+              pwComponents[i] = new StringBuilder(userPwComponents[i]);
             }
           }
-        }
-      }
-      else
-      {
-        for (Attribute a : attrList)
-        {
-          for (AttributeValue v : a.getValues())
+
+          String schemeName = pwComponents[0].toString();
+          PasswordStorageScheme scheme = (usesAuthPasswordSyntax)
+                    ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
+                    : DirectoryServer.getPasswordStorageScheme(schemeName);
+          if (scheme == null)
           {
-            try
+            if (debug)
             {
-              String[] pwComponents =
-                   UserPasswordSyntax.decodeUserPassword(v.getStringValue());
-              PasswordStorageScheme scheme =
-                   DirectoryServer.getPasswordStorageScheme(pwComponents[0]);
-              if (scheme == null)
-              {
-                if (debug)
-                {
-                  debugWarning(
-                      "User entry %s contains a password " +
-                                 "with scheme %s that is not " +
-                                 "defined in the server.",
-                             userDNString, pwComponents[0]);
-                }
-
-                continue;
-              }
-              else if (scheme.isReversible())
-              {
-                ByteString clearValue =
-                     scheme.getPlaintextValue(
-                          new ASN1OctetString(pwComponents[1]));
-                clearPasswords.add(clearValue);
-              }
+              debugWarning("User entry %s contains a password with scheme %s " +
+                   "that is not defined in the server.",
+                           userDNString, schemeName);
             }
-            catch (Exception e)
-            {
-              if (debugEnabled())
-              {
-                debugCaught(DebugLogLevel.ERROR, e);
-              }
 
-              if (debug)
-              {
-                debugWarning(
-                    "Cannot get clear password value for " +
-                        "user %s: %s", userDNString, e);
-              }
-            }
+            continue;
+          }
+
+          if (scheme.isReversible())
+          {
+            ByteString clearValue = (usesAuthPasswordSyntax)
+                         ? scheme.getAuthPasswordPlaintextValue(
+                               pwComponents[1].toString(),
+                               pwComponents[2].toString())
+                         : scheme.getPlaintextValue(
+                               new ASN1OctetString(pwComponents[1].toString()));
+            clearPasswords.add(clearValue);
+          }
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          if (debug)
+          {
+            debugWarning("Cannot get clear password value foruser %s: %s",
+                         userDNString, e);
           }
         }
       }
@@ -3249,122 +3029,80 @@
       return false;
     }
 
-
-    if (passwordPolicy.usesAuthPasswordSyntax())
+    for (Attribute a : attrList)
     {
-      for (Attribute a : attrList)
+      boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax();
+
+      for (AttributeValue v : a.getValues())
       {
-        for (AttributeValue v : a.getValues())
+        try
         {
-          try
+          StringBuilder[] pwComponents;
+          if (usesAuthPasswordSyntax)
           {
-            StringBuilder[] pwComponents =
+            pwComponents =
                  AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
-            PasswordStorageScheme scheme =
-                 DirectoryServer.getAuthPasswordStorageScheme(
-                                      pwComponents[0].toString());
-            if (scheme == null)
+          }
+          else
+          {
+            String[] userPwComponents =
+                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
+            pwComponents = new StringBuilder[userPwComponents.length];
+            for (int i = 0; i < userPwComponents.length; ++i)
             {
-              if (debug)
-              {
-                debugWarning(
-                    "User entry %s contains a password with scheme %s " +
-                                 "that is not defined in the server.",
-                             userDNString, pwComponents[0]);
-              }
-
-              continue;
-            }
-
-            if (scheme.authPasswordMatches(password, pwComponents[1].toString(),
-                                           pwComponents[2].toString()))
-            {
-              if (debug)
-              {
-                if (debugEnabled())
-                {
-                  debugInfo("Returning true for user %s because the provided " +
-                      "password matches a value encoded with scheme %s",
-                            userDNString, pwComponents[0]);
-                }
-              }
-
-              return true;
+              pwComponents[i] = new StringBuilder(userPwComponents[i]);
             }
           }
-          catch (Exception e)
-          {
-            if (debugEnabled())
-            {
-              debugCaught(DebugLogLevel.ERROR, e);
-            }
 
+          String schemeName = pwComponents[0].toString();
+          PasswordStorageScheme scheme = (usesAuthPasswordSyntax)
+                     ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
+                     : DirectoryServer.getPasswordStorageScheme(schemeName);
+          if (scheme == null)
+          {
             if (debug)
             {
-              debugError(
-                  "An error occurred while attempting to process a " +
-                      "password value for user %s: %s",
-                  userDNString, stackTraceToSingleLineString(e));
+              debugWarning("User entry %s contains a password with scheme %s " +
+                   "that is not defined in the server.",
+                           userDNString, schemeName);
             }
+
+            continue;
+          }
+
+          boolean passwordMatches = (usesAuthPasswordSyntax)
+                     ? scheme.authPasswordMatches(password,
+                                                  pwComponents[1].toString(),
+                                                  pwComponents[2].toString())
+                     : scheme.passwordMatches(password,
+                               new ASN1OctetString(pwComponents[1].toString()));
+          if (passwordMatches)
+          {
+            if (debug)
+            {
+              if (debugEnabled())
+              {
+                debugInfo("Returning true for user %s because the provided " +
+                    "password matches a value encoded with scheme %s",
+                          userDNString, schemeName);
+              }
+            }
+
+            return true;
           }
         }
-      }
-    }
-    else
-    {
-      for (Attribute a : attrList)
-      {
-        for (AttributeValue v : a.getValues())
+        catch (Exception e)
         {
-          try
+          if (debugEnabled())
           {
-            String[] pwComponents =
-                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
-            PasswordStorageScheme scheme =
-                 DirectoryServer.getPasswordStorageScheme(pwComponents[0]);
-            if (scheme == null)
-            {
-              if (debug)
-              {
-                debugWarning(
-                    "User entry %s contains a password with scheme %s " +
-                                 "that is not defined in the server.",
-                             userDNString, pwComponents[0]);
-              }
-
-              continue;
-            }
-
-            if (scheme.passwordMatches(password,
-                                       new ASN1OctetString(pwComponents[1])))
-            {
-              if (debug)
-              {
-                if (debugEnabled())
-                {
-                  debugInfo("Returning true for user %s because the provided " +
-                      "password matches a value encoded with scheme %s",
-                            userDNString, pwComponents[0]);
-                }
-              }
-
-              return true;
-            }
+            debugCaught(DebugLogLevel.ERROR, e);
           }
-          catch (Exception e)
-          {
-            if (debugEnabled())
-            {
-              debugCaught(DebugLogLevel.ERROR, e);
-            }
 
-            if (debug)
-            {
-              debugError(
-                  "An error occurred while attempting to process a " +
-                      "password value for user %s: %s",
-                  userDNString, stackTraceToSingleLineString(e));
-            }
+          if (debug)
+          {
+            debugWarning("An error occurred while attempting to process a " +
+                 "password value for user %s: %s",
+                         userDNString, stackTraceToSingleLineString(e));
           }
         }
       }
@@ -3553,354 +3291,187 @@
     LinkedHashSet<AttributeValue> updatedValues =
          new LinkedHashSet<AttributeValue>();
 
-    if (passwordPolicy.usesAuthPasswordSyntax())
-    {
-      for (Attribute a : attrList)
-      {
-        Iterator<AttributeValue> iterator = a.getValues().iterator();
-        while (iterator.hasNext())
-        {
-          AttributeValue v = iterator.next();
+    boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax();
 
-          try
+    for (Attribute a : attrList)
+    {
+      Iterator<AttributeValue> iterator = a.getValues().iterator();
+      while (iterator.hasNext())
+      {
+        AttributeValue v = iterator.next();
+
+        try
+        {
+          StringBuilder[] pwComponents;
+          if (usesAuthPasswordSyntax)
           {
-            StringBuilder[] pwComponents =
+            pwComponents =
                  AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
-            String schemeName = pwComponents[0].toString();
-            PasswordStorageScheme scheme =
-                 DirectoryServer.getAuthPasswordStorageScheme(schemeName);
-            if (scheme == null)
+          }
+          else
+          {
+            String[] userPwComponents =
+                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
+            pwComponents = new StringBuilder[userPwComponents.length];
+            for (int i = 0; i < userPwComponents.length; ++i)
+            {
+              pwComponents[i] = new StringBuilder(userPwComponents[i]);
+            }
+          }
+
+          String schemeName = pwComponents[0].toString();
+          PasswordStorageScheme scheme = (usesAuthPasswordSyntax)
+                    ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
+                    : DirectoryServer.getPasswordStorageScheme(schemeName);
+          if (scheme == null)
+          {
+            if (debug)
+            {
+              debugWarning("Skipping password value for user %s because the " +
+                   "associated storage scheme %s is not configured for use.",
+                           userDNString, schemeName);
+            }
+
+            continue;
+          }
+
+          boolean passwordMatches = (usesAuthPasswordSyntax)
+                     ? scheme.authPasswordMatches(password,
+                                                  pwComponents[1].toString(),
+                                                  pwComponents[2].toString())
+                     : scheme.passwordMatches(password,
+                               new ASN1OctetString(pwComponents[1].toString()));
+          if (passwordMatches)
+          {
+            if (passwordPolicy.isDefaultStorageScheme(schemeName))
+            {
+              existingDefaultSchemes.add(schemeName);
+              updatedValues.add(v);
+            }
+            else if (passwordPolicy.isDeprecatedStorageScheme(schemeName))
             {
               if (debug)
               {
-                debugWarning(
-                    "Skipping password value for user %s because the " +
-                                 "associated storage scheme %s is not " +
-                                 "configured for use.",
-                    userDNString, schemeName);
-              }
-
-              continue;
-            }
-
-            if (scheme.authPasswordMatches(password, pwComponents[1].toString(),
-                                           pwComponents[2].toString()))
-            {
-              if (passwordPolicy.isDefaultStorageScheme(schemeName))
-              {
-                existingDefaultSchemes.add(schemeName);
-                updatedValues.add(v);
-              }
-              else if (passwordPolicy.isDeprecatedStorageScheme(schemeName))
-              {
-                if (debug)
+                if (debugEnabled())
                 {
-                  if (debugEnabled())
-                  {
-                    debugInfo("Marking password with scheme %s for removal " +
-                        "from user entry %s", pwComponents[0], userDNString);
-                  }
+                  debugInfo("Marking password with scheme %s for removal " +
+                      "from user entry %s.", schemeName, userDNString);
                 }
-
-                iterator.remove();
-                removedValues.add(v);
               }
-              else
-              {
-                updatedValues.add(v);
-              }
-            }
-          }
-          catch (Exception e)
-          {
-            if (debugEnabled())
-            {
-              debugCaught(DebugLogLevel.ERROR, e);
-            }
 
-            if (debug)
+              iterator.remove();
+              removedValues.add(v);
+            }
+            else
             {
-              debugWarning(
-                  "Skipping password value for user %s because an " +
-                      "error occurred while attempting to decode it " +
-                      "based on the user password syntax: %s",
-                  userDNString, stackTraceToSingleLineString(e));
+              updatedValues.add(v);
             }
           }
         }
-      }
-
-      if (removedValues.isEmpty())
-      {
-        if (debug)
+        catch (Exception e)
         {
           if (debugEnabled())
           {
-            debugInfo("User entry %s does not have any password values " +
-                "encoded using deprecated schemes.", userDNString);
-          }
-        }
-      }
-      else
-      {
-        LinkedHashSet<AttributeValue> addedValues = new
-             LinkedHashSet<AttributeValue>();
-        for (PasswordStorageScheme s :
-             passwordPolicy.getDefaultStorageSchemes())
-        {
-          if (! existingDefaultSchemes.contains(
-                     toLowerCase(s.getStorageSchemeName())))
-          {
-            try
-            {
-              ByteString encodedPassword = s.encodeAuthPassword(password);
-              AttributeValue v = new AttributeValue(type, encodedPassword);
-              addedValues.add(v);
-              updatedValues.add(v);
-            }
-            catch (Exception e)
-            {
-              if (debugEnabled())
-              {
-                debugCaught(DebugLogLevel.ERROR, e);
-              }
-
-              if (debug)
-              {
-                debugWarning(
-                    "Unable to encode password for user %s using " +
-                        "default scheme %s: %s",
-                    userDNString, s.getStorageSchemeName(),
-                    stackTraceToSingleLineString(e));
-              }
-            }
-          }
-        }
-
-        if (updatedValues.isEmpty())
-        {
-          if (debug)
-          {
-            debugWarning(
-                "Not updating user entry %s because removing " +
-                             "deprecated schemes would leave the user " +
-                             "without a password.", userDNString);
-          }
-
-          return;
-        }
-        else
-        {
-          Attribute a = new Attribute(type, type.getNameOrOID(), removedValues);
-          if (! updateEntry)
-          {
-            modifications.add(new Modification(ModificationType.DELETE, a,
-                                               true));
-          }
-
-          if (! addedValues.isEmpty())
-          {
-            Attribute a2 = new Attribute(type, type.getNameOrOID(),
-                                         addedValues);
-            if (! updateEntry)
-            {
-              modifications.add(new Modification(ModificationType.ADD, a2,
-                                                 true));
-            }
-          }
-
-          ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
-          newList.add(new Attribute(type, type.getNameOrOID(), updatedValues));
-          if (updateEntry)
-          {
-            userEntry.putAttribute(type, newList);
+            debugCaught(DebugLogLevel.ERROR, e);
           }
 
           if (debug)
           {
-            if (debugEnabled())
-            {
-              debugInfo("Updating user entry %s to replace password values " +
-                  "encoded with deprecated schemes with values encoded " +
-                  "with the default schemes.", userDNString);
-            }
+            debugWarning("Skipping password value for user %s because an " +
+                 "error occurred while attempting to decode it " +
+                 "based on the user password syntax: %s",
+                         userDNString, stackTraceToSingleLineString(e));
           }
         }
       }
     }
-    else
+
+    if (removedValues.isEmpty())
     {
-      for (Attribute a : attrList)
+      if (debug)
       {
-        Iterator<AttributeValue> iterator = a.getValues().iterator();
-        while (iterator.hasNext())
+        if (debugEnabled())
         {
-          AttributeValue v = iterator.next();
-
-          try
-          {
-            String[] pwComponents =
-                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
-            PasswordStorageScheme scheme =
-                 DirectoryServer.getPasswordStorageScheme(pwComponents[0]);
-            if (scheme == null)
-            {
-              if (debug)
-              {
-                debugWarning(
-                    "Skipping password value for user %s because the " +
-                                 "associated storage scheme %s is not " +
-                                 "configured for use.",
-                             userDNString, pwComponents[0]);
-              }
-
-              continue;
-            }
-
-            if (scheme.passwordMatches(password,
-                                       new ASN1OctetString(pwComponents[1])))
-            {
-              if (passwordPolicy.isDefaultStorageScheme(pwComponents[0]))
-              {
-                existingDefaultSchemes.add(pwComponents[0]);
-                updatedValues.add(v);
-              }
-              else if (passwordPolicy.isDeprecatedStorageScheme(
-                                           pwComponents[0]))
-              {
-                if (debug)
-                {
-                  if (debugEnabled())
-                  {
-                    debugInfo("Marking password with scheme %s for removal " +
-                        "from user entry %s", pwComponents[0], userDNString);
-                  }
-                }
-
-                iterator.remove();
-                removedValues.add(v);
-              }
-              else
-              {
-                updatedValues.add(v);
-              }
-            }
-          }
-          catch (Exception e)
-          {
-            if (debugEnabled())
-            {
-              debugCaught(DebugLogLevel.ERROR, e);
-            }
-
-            if (debug)
-            {
-              debugWarning(
-                  "Skipping password value for user %s because an error " +
-                      "occurred while attempting to decode it based on " +
-                      "the user password syntax: %s",
-                  userDNString, stackTraceToSingleLineString(e));
-            }
-          }
+          debugInfo("User entry %s does not have any password values " +
+              "encoded using deprecated schemes.", userDNString);
         }
       }
 
-      if (removedValues.isEmpty())
+      return;
+    }
+
+    LinkedHashSet<AttributeValue> addedValues = new
+         LinkedHashSet<AttributeValue>();
+    for (PasswordStorageScheme s :
+         passwordPolicy.getDefaultStorageSchemes())
+    {
+      if (! existingDefaultSchemes.contains(
+           toLowerCase(s.getStorageSchemeName())))
       {
-        if (debug)
+        try
+        {
+          ByteString encodedPassword = (usesAuthPasswordSyntax)
+                                       ? s.encodeAuthPassword(password)
+                                       : s.encodePasswordWithScheme(password);
+          AttributeValue v = new AttributeValue(type, encodedPassword);
+          addedValues.add(v);
+          updatedValues.add(v);
+        }
+        catch (Exception e)
         {
           if (debugEnabled())
           {
-            debugInfo("User entry %s does not have any password values " +
-                "encoded using deprecated schemes.", userDNString);
+            debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          if (debug)
+          {
+            debugWarning("Unable to encode password for user %s using " +
+                 "default scheme %s: %s",
+                         userDNString, s.getStorageSchemeName(),
+                         stackTraceToSingleLineString(e));
           }
         }
       }
-      else
+    }
+
+    if (updatedValues.isEmpty())
+    {
+      if (debug)
       {
-        LinkedHashSet<AttributeValue> addedValues = new
-             LinkedHashSet<AttributeValue>();
-        for (PasswordStorageScheme s :
-             passwordPolicy.getDefaultStorageSchemes())
-        {
-          if (! existingDefaultSchemes.contains(
-                     toLowerCase(s.getStorageSchemeName())))
-          {
-            try
-            {
-              ByteString encodedPassword = s.encodePasswordWithScheme(password);
-              AttributeValue v = new AttributeValue(type, encodedPassword);
-              addedValues.add(v);
-              updatedValues.add(v);
-            }
-            catch (Exception e)
-            {
-              if (debugEnabled())
-              {
-                debugCaught(DebugLogLevel.ERROR, e);
-              }
+        debugWarning("Not updating user entry %s because removing " +
+             "deprecated schemes would leave the user without a password.",
+                     userDNString);
+      }
 
-              if (debug)
-              {
-                debugWarning(
-                    "Unable to encode password for user %s using " +
-                        "default scheme %s: %s",
-                    userDNString, s.getStorageSchemeName(),
-                    stackTraceToSingleLineString(e));
-              }
-            }
-          }
-        }
+      return;
+    }
 
-        if (updatedValues.isEmpty())
-        {
-          if (debug)
-          {
-            debugWarning(
-                "Not updating user entry %s because removing " +
-                             "deprecated schemes would leave the user " +
-                             "without a password.", userDNString);
-          }
+    if (updateEntry)
+    {
+      ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
+      newList.add(new Attribute(type, type.getNameOrOID(), updatedValues));
+      userEntry.putAttribute(type, newList);
+    }
+    else
+    {
+      Attribute a = new Attribute(type, type.getNameOrOID(), removedValues);
+      modifications.add(new Modification(ModificationType.DELETE, a, true));
 
-          return;
-        }
-        else
-        {
-          Attribute a = new Attribute(type, type.getNameOrOID(), removedValues);
-          if (! updateEntry)
-          {
-            modifications.add(new Modification(ModificationType.DELETE, a,
-                                               true));
-          }
+      if (! addedValues.isEmpty())
+      {
+        Attribute a2 = new Attribute(type, type.getNameOrOID(), addedValues);
+        modifications.add(new Modification(ModificationType.ADD, a2, true));
+      }
+    }
 
-          if (! addedValues.isEmpty())
-          {
-            Attribute a2 = new Attribute(type, type.getNameOrOID(),
-                                         addedValues);
-            if (! updateEntry)
-            {
-              modifications.add(new Modification(ModificationType.ADD, a2,
-                                                 true));
-            }
-          }
-
-          ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
-          newList.add(new Attribute(type, type.getNameOrOID(), updatedValues));
-          if (updateEntry)
-          {
-            userEntry.putAttribute(type, newList);
-          }
-
-          if (debug)
-          {
-            if (debugEnabled())
-            {
-              debugInfo("Updating user entry %sto replace password values " +
-                  "encoded with deprecated schemes with values encoded " +
-                  "with the default schemes.", userDNString);
-            }
-          }
-        }
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        debugInfo("Updating user entry %s to replace password values " +
+            "encoded with deprecated schemes with values encoded " +
+            "with the default schemes.", userDNString);
       }
     }
   }
@@ -3924,10 +3495,9 @@
     {
       if (debug)
       {
-        debugWarning(
-            "Unable to generate a new password for user %s " +
-                         "because no password generator has been " +
-                         "defined in the associated password policy.",
+        debugWarning("Unable to generate a new password for user %s because " +
+             "no password generator has been defined in the associated " +
+             "password policy.",
                      userDNString);
       }
 
@@ -3993,6 +3563,21 @@
 
 
   /**
+   * Retrieves the set of modifications that correspond to changes made in
+   * password policy processing that may need to be applied to the user entry.
+   *
+   * @return  The set of modifications that correspond to changes made in
+   *          password policy processing that may need to be applied to the user
+   *          entry.
+   */
+  public LinkedList<Modification> getModifications()
+  {
+    return modifications;
+  }
+
+
+
+  /**
    * Performs an internal modification to update the user's entry, if necessary.
    * This will do nothing if no modifications are required.
    *
@@ -4033,4 +3618,3 @@
     }
   }
 }
-
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
index 84d4d80..2853b65 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -1101,7 +1101,6 @@
 
       // Clear any record of grace logins, auth failures, and expiration
       // warnings.
-      pwPolicyState.clearAuthFailureTimes();
       pwPolicyState.clearFailureLockout();
       pwPolicyState.clearGraceLoginTimes();
       pwPolicyState.clearWarnedTime();

--
Gitblit v1.10.0