mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

jdemendi
02.29.2007 bff247202b2e096249150884a93601b19e836c9a
Issue #1560 - Migrate some objects to the new admin framework:
- Entry Cache
- Account Status Notification Handler
- Password Storage Scheme

7 files added
35 files modified
6555 ■■■■■ changed files
opends/resource/FindJavaHome.class patch | view | raw | blame | history
opends/resource/admin/abbreviations.xsl 4 ●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/AccountStatusNotificationHandlerConfiguration.xml 65 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/EntryCacheConfiguration.xml 60 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/ErrorLogAccountStatusNotificationHandlerConfiguration.xml 135 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/FIFOEntryCacheConfiguration.xml 112 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/Package.xml 39 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/PasswordStorageSchemeConfiguration.xml 61 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml 24 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/SoftReferenceEntryCacheConfiguration.xml 50 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/AccountStatusNotificationHandler.java 18 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/EntryCache.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/PasswordStorageScheme.java 16 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AccountStatusNotificationHandlerConfigManager.java 1083 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/EntryCacheConfigManager.java 676 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordStorageSchemeConfigManager.java 1072 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/Base64PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ClearPasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/DefaultEntryCache.java 167 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/EntryCacheCommon.java 333 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandler.java 414 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/FIFOEntryCache.java 1100 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/MD5PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SHA1PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SoftReferenceEntryCache.java 866 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/Base64PasswordStorageSchemeTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ClearPasswordStorageSchemeTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/MD5PasswordStorageSchemeTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SHA1PasswordStorageSchemeTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedMD5PasswordStorageSchemeTestCase.java 14 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA1PasswordStorageSchemeTestCase.java 21 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA256PasswordStorageSchemeTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA384PasswordStorageSchemeTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA512PasswordStorageSchemeTestCase.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AuthPasswordEqualityMatchingRuleTest.java 15 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/UserPasswordEqualityMatchingRuleTest.java 12 ●●●●● patch | view | raw | blame | history
opends/resource/FindJavaHome.class
Binary files differ
opends/resource/admin/abbreviations.xsl
@@ -47,6 +47,8 @@
              or $value = 'ldap' or $value = 'ldaps' or $value = 'ldif'
              or $value = 'jdbc' or $value = 'tcp' or $value = 'tls'
              or $value = 'pkcs11' or $value = 'sasl' or $value = 'gssapi'
              or $value = 'md5' or $value = 'je' or $value = 'dse' " />
              or $value = 'md5' or $value = 'je' or $value = 'dse'
              or $value = 'fifo'
             "/>
  </xsl:template>
</xsl:stylesheet>
opends/src/admin/defn/org/opends/server/admin/std/AccountStatusNotificationHandlerConfiguration.xml
New file
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<adm:managed-object
  name="account-status-notification-handler"
  plural-name="account-status-notification-handlers"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  >
  <adm:synopsis>
    <adm:user-friendly-name />
    is invoked whenever certain types of events occur that could change
    the status of a user account. The
    <adm:user-friendly-name />
    may be used to notify the user and/or administrators of the change.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.75</ldap:oid>
      <ldap:name>ds-cfg-account-status-notification-handler</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="enabled" mandatory="true">
    <adm:synopsis>
      Indicate whether the
      <adm:user-friendly-name />
      is enabled for use.
    </adm:synopsis>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.282</ldap:oid>
        <ldap:name>ds-cfg-account-status-notification-handler-enabled</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="notification-handler-class" mandatory="true">
    <adm:synopsis>
      The fully-qualified name of the Java class that provides the
      <adm:user-friendly-name />
      implementation.
    </adm:synopsis>
    <adm:syntax>
      <adm:java-class>
        <adm:instance-of>
          org.opends.server.api.AccountStatusNotificationHandler
        </adm:instance-of>
      </adm:java-class>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.281</ldap:oid>
        <ldap:name>ds-cfg-account-status-notification-handler-class</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/EntryCacheConfiguration.xml
New file
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<adm:managed-object
  name="entry-cache"
  plural-name="entry-caches"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  >
  <adm:synopsis>
    <adm:user-friendly-name />
    defines a Directory Server entry cache.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.15</ldap:oid>
      <ldap:name>ds-cfg-entry-cache</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="enabled" mandatory="true">
    <adm:synopsis>
      Indicate whether the
      <adm:user-friendly-name />
      is enabled for use.
    </adm:synopsis>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.35</ldap:oid>
        <ldap:name>ds-cfg-entry-cache-enabled</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="entry-cache-class" mandatory="true">
    <adm:synopsis>
      The fully-qualified name of the Java class that provides the
      <adm:user-friendly-name />
      implementation.
    </adm:synopsis>
    <adm:syntax>
      <adm:java-class>
        <adm:instance-of>
          org.opends.server.api.EntryCache
        </adm:instance-of>
      </adm:java-class>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.34</ldap:oid>
        <ldap:name>ds-cfg-entry-cache-class</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/ErrorLogAccountStatusNotificationHandlerConfiguration.xml
New file
@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<adm:managed-object
  name="error-log-account-status-notification-handler"
  plural-name="error-log-account-status-notification-handlers"
  extends="account-status-notification-handler"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  >
  <adm:synopsis>
    <adm:user-friendly-name />
    is an account status notification handler that writes information
    about status notifications using the Directory Server's error
    logging facility.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.76</ldap:oid>
      <ldap:name>
        ds-cfg-error-log-account-status-notification-handler
      </ldap:name>
      <ldap:superior>
        ds-cfg-account-status-notification-handler
      </ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="account-status-notification-type" mandatory="true"
    multi-valued="true">
    <adm:synopsis>
      <adm:user-friendly-name />
      is a possible event type that can trigger an account status
      notification.
    </adm:synopsis>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="account-temporarily-locked">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user account has been temporarily locked after
            too many failed attempts.
          </adm:synopsis>
        </adm:value>
        <adm:value name="account-permanently-locked">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user account has been permanently locked after
            too many failed attempts.
          </adm:synopsis>
        </adm:value>
        <adm:value name="account-unlocked">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user account has been unlocked by an
            administrator.
          </adm:synopsis>
        </adm:value>
        <adm:value name="account-idle-locked">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user account has been locked because it was idle
            for too long.
          </adm:synopsis>
        </adm:value>
        <adm:value name="account-reset-locked">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user account has been locked because it the
            password had been reset by an administrator but not changed
            by the user within the required interval.
          </adm:synopsis>
        </adm:value>
        <adm:value name="account-disabled">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user account has been disabled by an
            administrator.
          </adm:synopsis>
        </adm:value>
        <adm:value name="account-enabled">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user account has been enabled by an
            administrator.
          </adm:synopsis>
        </adm:value>
        <adm:value name="account-expired">
          <adm:synopsis>
            Indicates that an account status message should be generated
            whenever a user authentication has failed because the
            account has expired.
          </adm:synopsis>
        </adm:value>
        <adm:value name="password-expired">
          <adm:synopsis>
            Indicates that an account status notification message should
            be generated whenever a user authentication has failed
            because the password has expired.
          </adm:synopsis>
        </adm:value>
        <adm:value name="password-expiring">
          <adm:synopsis>
            Indicates that an account status notification message should
            be generated the first time that a password expiration
            warning is encountered for a user password.
          </adm:synopsis>
        </adm:value>
        <adm:value name="password-reset">
          <adm:synopsis>
            Indicates that an account status notification message should
            be generated whenever a user's password is reset by an
            administrator.
          </adm:synopsis>
        </adm:value>
        <adm:value name="password-changed">
          <adm:synopsis>
            Indicates whether an account status notification message
            should be generated whenever a user changes his/her own
            password.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.283</ldap:oid>
        <ldap:name>ds-cfg-account-status-notification-type</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/FIFOEntryCacheConfiguration.xml
New file
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<adm:managed-object
  name="fifo-entry-cache"
  plural-name="fifo-entry-caches"
  package="org.opends.server.admin.std"
  extends="entry-cache"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  >
  <adm:synopsis>
    <adm:user-friendly-name />
    defines a Directory Server entry cache that uses a FIFO to keep
    track of the entries.  Entries that have been in the cache the longest are
    the most likely candidates for purging if space is needed.  In contrast to
    other cache structures, the selection of entries to purge is not based on
    how frequently or recently the entries have been accessed.  This requires
    significantly less locking (it will only be required when an entry is added
    or removed from the cache, rather than each time an entry is accessed).
    Cache sizing is based on the percentage of free memory within the JVM, such
    that if enough memory is free, then adding an entry to the cache will not
    require purging, but if more than a specified percentage of the available
    memory within the JVM is already consumed, then one or more entries will need
    to be removed in order to make room for a new entry.  It is also possible to
    configure a maximum number of entries for the cache.  If this is specified,
    then the number of entries will not be allowed to exceed this value, but it
    may not be possible to hold this many entries if the available memory fills
    up first.
    Other configurable parameters for this cache include the maximum length of
    time to block while waiting to acquire a lock, and a set of filters that may
    be used to define criteria for determining which entries are stored in the
    cache.  If a filter list is provided, then only entries matching at least
    one of the given filters will be stored in the cache.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.16</ldap:oid>
      <ldap:name>ds-cfg-fifo-entry-cache</ldap:name>
      <ldap:superior>ds-cfg-entry-cache</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="lock-timeout" mandatory="false">
    <adm:synopsis>
      The length of time in milliseconds to wait while
      attempting to acquire a read or write lock.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>2000.0ms</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:duration base-unit="ms" lower-limit="0" allow-unlimited="true"/>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.58</ldap:oid>
        <ldap:name>ds-cfg-lock-timeout</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="max-memory-percent" mandatory="false">
    <adm:synopsis>
      The maximum memory usage for the entry cache as a percentage
      of the total JVM memory.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>90</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:size lower-limit="1" upper-limit="100"/>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.66</ldap:oid>
        <ldap:name>ds-cfg-max-memory-percent</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="max-entries" mandatory="false">
    <adm:synopsis>
      The maximum number of entries that we will allow in the cache.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>0x7fffffffffffffffL</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:size lower-limit="0" allow-unlimited="true"/>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.65</ldap:oid>
        <ldap:name>ds-cfg-max-entries</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property-reference name="include-filter" />
  <adm:property-reference name="exclude-filter" />
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/Package.xml
@@ -242,4 +242,43 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="include-filter" mandatory="false" multi-valued="true">
    <adm:synopsis>
      The set of filters that define the entries that should be
      included in the cache.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:undefined />
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.43</ldap:oid>
        <ldap:name>ds-cfg-include-filter</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="exclude-filter" mandatory="false" multi-valued="true">
    <adm:synopsis>
      The set of filters that define the entries that should be
      excluded from the cache.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:undefined />
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.38</ldap:oid>
        <ldap:name>ds-cfg-exclude-filter</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:package>
opends/src/admin/defn/org/opends/server/admin/std/PasswordStorageSchemeConfiguration.xml
New file
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<adm:managed-object
  name="password-storage-scheme"
  plural-name="password-storage-schemes"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  >
  <adm:synopsis>
    <adm:user-friendly-name />
    defines a module that implements a password storage scheme.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.35</ldap:oid>
      <ldap:name>ds-cfg-password-storage-scheme</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="enabled" mandatory="true">
    <adm:synopsis>
      Indicate whether the
      <adm:user-friendly-name />
      is enabled for use.
    </adm:synopsis>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.76</ldap:oid>
        <ldap:name>ds-cfg-password-storage-scheme-enabled</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="scheme-class" mandatory="true">
    <adm:synopsis>
      The fully-qualified name of the Java class that provides the
      <adm:user-friendly-name />
      implementation.
    </adm:synopsis>
    <adm:syntax>
      <adm:java-class>
        <adm:instance-of>
          org.opends.server.api.PasswordStorageScheme
        </adm:instance-of>
      </adm:java-class>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.75</ldap:oid>
        <ldap:name>ds-cfg-password-storage-scheme-class</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml
@@ -99,6 +99,30 @@
      </ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:relation name="entry-cache">
    <adm:one-to-one />
    <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Entry Cache,cn=config
      </ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:relation name="account-status-notification-handler">
    <adm:one-to-many />
    <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Account Status Notification Handlers,cn=config
      </ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:relation name="password-storage-scheme">
    <adm:one-to-many />
    <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Password Storage Schemes,cn=config
      </ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:relation name="backend">
    <adm:one-to-many />
    <adm:profile name="ldap">
opends/src/admin/defn/org/opends/server/admin/std/SoftReferenceEntryCacheConfiguration.xml
New file
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<adm:managed-object
  name="soft-reference-entry-cache"
  plural-name="soft-reference-entry-caches"
  package="org.opends.server.admin.std"
  extends="entry-cache"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  >
  <adm:synopsis>
    <adm:user-friendly-name />
    defines a Directory Server entry cache that uses soft references
    to manage objects in a way that will allow them to be freed if the JVM is
    running low on memory.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.17</ldap:oid>
      <ldap:name>ds-cfg-soft-reference-entry-cache</ldap:name>
      <ldap:superior>ds-cfg-entry-cache</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="lock-timeout" mandatory="false">
    <adm:synopsis>
      The length of time in milliseconds to wait while
      attempting to acquire a read or write lock.
    </adm:synopsis>
    <adm:default-behavior>
        <adm:defined>
            <adm:value>3000ms</adm:value>
        </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:duration base-unit="ms" lower-limit="0" allow-unlimited="true"/>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.58</ldap:oid>
        <ldap:name>ds-cfg-lock-timeout</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property-reference name="include-filter" />
  <adm:property-reference name="exclude-filter" />
</adm:managed-object>
opends/src/server/org/opends/server/api/AccountStatusNotificationHandler.java
@@ -28,7 +28,8 @@
import org.opends.server.config.ConfigEntry;
import org.opends.server.admin.std.server.
       AccountStatusNotificationHandlerCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.types.AccountStatusNotification;
import org.opends.server.types.AccountStatusNotificationType;
@@ -44,16 +45,21 @@
 * the status of a user account.  The account status notification
 * handler may be used to notify the user and/or administrators of the
 * change.
 *
 * @param  <T>  The type of configuration handled by this notification
 *              handler.
 */
public abstract class AccountStatusNotificationHandler
public abstract class
       AccountStatusNotificationHandler
       <T extends AccountStatusNotificationHandlerCfg>
{
  /**
   * Initializes this account status notification handler based on the
   * information in the provided configuration entry.
   *
   * @param  configEntry  The configuration entry that contains the
   *                      information to use to initialize this
   *                      account status notification handler.
   * @param  configuration  The configuration entry that contains the
   *                        information to use to initialize this
   *                        account status notification handler.
   *
   * @throws  ConfigException  If the provided entry does not contain
   *                           a valid configuration for this account
@@ -65,7 +71,7 @@
   *                                   configuration.
   */
  public abstract void initializeStatusNotificationHandler(
                            ConfigEntry configEntry)
         T configuration)
         throws ConfigException, InitializationException;
opends/src/server/org/opends/server/api/EntryCache.java
@@ -31,12 +31,12 @@
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LockType;
import org.opends.server.admin.std.server.EntryCacheCfg;
@@ -62,15 +62,19 @@
 *       the behavior of the default cache that will be used if none
 *       is configured).</LI>
 * </UL>
 *
 * @param  <T>  The type of configuration handled by this entry
 *              cache.
 */
public abstract class EntryCache
       <T extends EntryCacheCfg>
{
  /**
   * Initializes this entry cache implementation so that it will be
   * available for storing and retrieving entries.
   *
   * @param  configEntry  The configuration entry containing the
   *                      settings to use for this entry cache.
   * @param  configuration  The configuration to use to initialize
   *                        the entry cache.
   *
   * @throws  ConfigException  If there is a problem with the provided
   *                           configuration entry that would prevent
@@ -81,7 +85,7 @@
   *                                   not related to the
   *                                   configuration.
   */
  public abstract void initializeEntryCache(ConfigEntry configEntry)
  public abstract void initializeEntryCache(T configuration)
         throws ConfigException, InitializationException;
opends/src/server/org/opends/server/api/PasswordStorageScheme.java
@@ -28,7 +28,7 @@
import org.opends.server.config.ConfigEntry;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ByteString;
import org.opends.server.types.DirectoryException;
@@ -41,8 +41,12 @@
 * implemented by a Directory Server module that implements a password
 * storage scheme.  Each subclass may only implement a single password
 * storage scheme type.
 *
 * @param  <T>  The type of configuration handled by this
 *              password storage scheme
 */
public abstract class PasswordStorageScheme
public abstract class
       PasswordStorageScheme <T extends PasswordStorageSchemeCfg>
{
  /**
   * Initializes this password storage scheme handler based on the
@@ -50,9 +54,9 @@
   * register itself with the Directory Server for the particular
   * storage scheme that it will manage.
   *
   * @param  configEntry  The configuration entry that contains the
   *                      information to use to initialize this
   *                      password storage scheme handler.
   * @param  configuration  The configuration entry that contains the
   *                        information to use to initialize this
   *                        password storage scheme handler.
   *
   * @throws  ConfigException  If an unrecoverable problem arises in
   *                           the process of performing the
@@ -64,7 +68,7 @@
   *                                   configuration.
   */
  public abstract void initializePasswordStorageScheme(
                            ConfigEntry configEntry)
         T configuration)
         throws ConfigException, InitializationException;
opends/src/server/org/opends/server/core/AccountStatusNotificationHandlerConfigManager.java
@@ -28,38 +28,30 @@
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.meta.AccountStatusNotificationHandlerCfgDefn;
import org.opends.server.admin.std.server.AccountStatusNotificationHandlerCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.api.ConfigAddListener;
import org.opends.server.api.ConfigChangeListener;
import org.opends.server.api.ConfigDeleteListener;
import org.opends.server.api.ConfigHandler;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.config.BooleanConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.StringConfigAttribute;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
/**
@@ -70,19 +62,16 @@
 * server is running.
 */
public class AccountStatusNotificationHandlerConfigManager
       implements ConfigChangeListener, ConfigAddListener, ConfigDeleteListener
       implements
          ConfigurationChangeListener <AccountStatusNotificationHandlerCfg>,
          ConfigurationAddListener    <AccountStatusNotificationHandlerCfg>,
          ConfigurationDeleteListener <AccountStatusNotificationHandlerCfg>
{
  // A mapping between the DNs of the config entries and the associated
  // notification handlers.
  private ConcurrentHashMap<DN,AccountStatusNotificationHandler>
               notificationHandlers;
  // The configuration handler for the Directory Server.
  private ConfigHandler configHandler;
          notificationHandlers;
  /**
@@ -91,7 +80,6 @@
   */
  public AccountStatusNotificationHandlerConfigManager()
  {
    configHandler = DirectoryServer.getConfigHandler();
    notificationHandlers =
         new ConcurrentHashMap<DN,AccountStatusNotificationHandler>();
  }
@@ -115,823 +103,228 @@
  public void initializeNotificationHandlers()
         throws ConfigException, InitializationException
  {
    // First, get the configuration base entry.
    ConfigEntry baseEntry;
    try
    // Get the root configuration object.
    ServerManagementContext managementContext =
      ServerManagementContext.getInstance();
    RootCfg rootConfiguration =
      managementContext.getRootConfiguration();
    // Register as an add and delete listener with the root configuration so
    // we can be notified if any account status notification handler entry
    // is added or removed.
    rootConfiguration.addAccountStatusNotificationHandlerAddListener (this);
    rootConfiguration.addAccountStatusNotificationHandlerDeleteListener (this);
    // Initialize existing account status notification handlers.
    for (String handlerName:
         rootConfiguration.listAccountStatusNotificationHandlers())
    {
      DN handlerBase = DN.decode(DN_ACCT_NOTIFICATION_HANDLER_CONFIG_BASE);
      baseEntry = configHandler.getConfigEntry(handlerBase);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      // Get the account status notification handler's configuration.
      AccountStatusNotificationHandlerCfg config =
        rootConfiguration.getAccountStatusNotificationHandler (handlerName);
      // Register as a change listener for this notification handler
      // entry so that we will be notified of any changes that may be
      // made to it.
      config.addChangeListener (this);
      // Ignore this notification handler if it is disabled.
      if (config.isEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
        // Load the notification handler implementation class.
        String className = config.getNotificationHandlerClass();
        loadAndInstallNotificationHandler (className, config);
      }
      int    msgID   = MSGID_CONFIG_ACCTNOTHANDLER_CANNOT_GET_BASE;
      String message = getMessage(msgID, String.valueOf(e));
      throw new ConfigException(msgID, message, e);
    }
  }
    if (baseEntry == null)
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      AccountStatusNotificationHandlerCfg configuration,
      List<String>  unacceptableReasons
      )
  {
    // returned status -- all is fine by default
    boolean status = true;
    if (configuration.isEnabled())
    {
      // The notification handler base entry does not exist.  This is not
      // acceptable, so throw an exception.
      int    msgID   = MSGID_CONFIG_ACCTNOTHANDLER_BASE_DOES_NOT_EXIST;
      String message = getMessage(msgID);
      throw new ConfigException(msgID, message);
    }
    // Register add and delete listeners with the notification handler base
    // entry.  We don't care about modifications to it.
    baseEntry.registerAddListener(this);
    baseEntry.registerDeleteListener(this);
    // See if the base entry has any children.  If not, then we don't need to do
    // anything else.
    if (! baseEntry.hasChildren())
    {
      return;
    }
    // Iterate through the child entries and process them as account status
    // notification handler configuration entries.
    for (ConfigEntry childEntry : baseEntry.getChildren().values())
    {
      childEntry.registerChangeListener(this);
      StringBuilder unacceptableReason = new StringBuilder();
      if (! configAddIsAcceptable(childEntry, unacceptableReason))
      {
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 MSGID_CONFIG_ACCTNOTHANDLER_ENTRY_UNACCEPTABLE,
                 childEntry.getDN().toString(), unacceptableReason.toString());
        continue;
      }
      // Get the name of the class and make sure we can instantiate it as an
      // entry cache.
      String className = configuration.getNotificationHandlerClass();
      try
      {
        ConfigChangeResult result = applyConfigurationAdd(childEntry);
        if (result.getResultCode() != ResultCode.SUCCESS)
        {
          StringBuilder buffer = new StringBuilder();
          List<String> resultMessages = result.getMessages();
          if ((resultMessages == null) || (resultMessages.isEmpty()))
          {
            buffer.append(getMessage(MSGID_CONFIG_UNKNOWN_UNACCEPTABLE_REASON));
          }
          else
          {
            Iterator<String> iterator = resultMessages.iterator();
            buffer.append(iterator.next());
            while (iterator.hasNext())
            {
              buffer.append(EOL);
              buffer.append(iterator.next());
            }
          }
          logError(ErrorLogCategory.CONFIGURATION,
                   ErrorLogSeverity.SEVERE_ERROR,
                   MSGID_CONFIG_ACCTNOTHANDLER_CANNOT_CREATE_HANDLER,
                   childEntry.getDN().toString(), buffer.toString());
        }
        // Load the class but don't initialize it.
        loadNotificationHandler(className, null);
      }
      catch (Exception e)
      catch (InitializationException ie)
      {
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 MSGID_CONFIG_ACCTNOTHANDLER_CANNOT_CREATE_HANDLER,
                 childEntry.getDN().toString(), String.valueOf(e));
        unacceptableReasons.add(ie.getMessage());
        status = false;
      }
    }
    return status;
  }
  /**
   * Indicates whether the configuration entry that will result from a proposed
   * modification is acceptable to this change listener.
   *
   * @param  configEntry         The configuration entry that will result from
   *                             the requested update.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed change is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry contains an acceptable
   *          configuration, or <CODE>false</CODE> if it does not.
   * {@inheritDoc}
   */
  public boolean configChangeIsAcceptable(ConfigEntry configEntry,
                                          StringBuilder unacceptableReason)
  public ConfigChangeResult applyConfigurationChange(
      AccountStatusNotificationHandlerCfg configuration
      )
  {
    // Make sure that the entry has an appropriate objectclass for an account
    // status notification handler.
    if (! configEntry.hasObjectClass(OC_ACCT_NOTIFICATION_HANDLER))
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    // Get the configuration entry DN and the associated handler class.
    DN configEntryDN = configuration.dn();
    AccountStatusNotificationHandler handler = notificationHandlers.get(
        configEntryDN
        );
    // If the new configuration has the notification handler disabled,
    // then remove it from the mapping list and clean it.
    if (! configuration.isEnabled())
    {
      int    msgID   = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_OBJECTCLASS;
      String message = getMessage(msgID, configEntry.getDN().toString());
      unacceptableReason.append(message);
      return false;
    }
    // Make sure that the entry specifies the notification handler class name.
    StringConfigAttribute classNameAttr;
    try
    {
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_CLASS_NAME;
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_CLASS,
                                     getMessage(msgID), true, false, true);
      classNameAttr = (StringConfigAttribute)
                      configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      if (handler != null)
      {
        msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_CLASS_NAME;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
        uninstallNotificationHandler (configEntryDN);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
      return changeResult;
    }
    Class handlerClass;
    try
    {
      handlerClass = DirectoryServer.loadClass(classNameAttr.pendingValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    try
    {
      AccountStatusNotificationHandler handler =
           (AccountStatusNotificationHandler) handlerClass.newInstance();
    }
    catch(Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS;
      String message = getMessage(msgID, handlerClass.getName(),
                                  String.valueOf(configEntry.getDN()),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // See if this account status notification handler should be enabled.
    BooleanConfigAttribute enabledAttr;
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_ENABLED,
                    getMessage(MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      {
        int msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_ENABLED_ATTR;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_ENABLED_VALUE;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // If we've gotten here then the notification handler entry appears to be
    // acceptable.
    return true;
  }
  /**
   * Attempts to apply a new configuration to this Directory Server component
   * based on the provided changed entry.
   *
   * @param  configEntry  The configuration entry that containing the updated
   *                      configuration for this component.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   */
  public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry)
  {
    DN                configEntryDN       = configEntry.getDN();
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // Make sure that the entry has an appropriate objectclass for an account
    // status notification handler.
    if (! configEntry.hasObjectClass(OC_ACCT_NOTIFICATION_HANDLER))
    {
      int    msgID   = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_OBJECTCLASS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
      resultCode = ResultCode.UNWILLING_TO_PERFORM;
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Get the corresponding notification handler if it is active.
    AccountStatusNotificationHandler handler =
         notificationHandlers.get(configEntryDN);
    // See if this handler should be enabled or disabled.
    boolean needsEnabled = false;
    BooleanConfigAttribute enabledAttr;
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_ENABLED,
                    getMessage(MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      {
        int msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_ENABLED_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.UNWILLING_TO_PERFORM;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      if (enabledAttr.activeValue())
      {
        if (handler == null)
        {
          needsEnabled = true;
        }
        else
        {
          // The handler is already active, so no action is required.
        }
      }
      else
      {
        if (handler == null)
        {
          // The handler is already disabled, so no action is required and we
          // can short-circuit out of this processing.
          return new ConfigChangeResult(resultCode, adminActionRequired,
                                        messages);
        }
        else
        {
          // The handler is active, so it needs to be disabled.  Do this and
          // return that we were successful.
          notificationHandlers.remove(configEntryDN);
          handler.finalizeStatusNotificationHandler();
          DirectoryServer.deregisterAccountStatusNotificationHandler(
               configEntryDN);
          return new ConfigChangeResult(resultCode, adminActionRequired,
                                        messages);
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_ENABLED_VALUE;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Make sure that the entry specifies the notification handler class name.
    // If it has changed, then we will not try to dynamically apply it.
    String className;
    try
    {
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_CLASS_NAME;
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_CLASS,
                                     getMessage(msgID), true, false, true);
      StringConfigAttribute classNameAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      {
        msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_CLASS_NAME;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      className = classNameAttr.pendingValue();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS_NAME;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    boolean classChanged = false;
    String  oldClassName = null;
    // At this point, new configuration is enabled...
    // If the current notification handler is already enabled then we
    // don't do anything unless the class has changed in which case we
    // should indicate that administrative action is required.
    String newClassName = configuration.getNotificationHandlerClass();
    if (handler != null)
    {
      oldClassName = handler.getClass().getName();
      classChanged = (! className.equals(oldClassName));
      String curClassName = handler.getClass().getName();
      boolean classIsNew = (! newClassName.equals (curClassName));
      if (classIsNew)
      {
        changeResult.setAdminActionRequired (true);
      }
      return changeResult;
    }
    if (classChanged)
    // New entry cache is enabled and there were no previous one.
    // Instantiate the new class and initalize it.
    try
    {
      // This will not be applied dynamically.  Add a message to the response
      // and indicate that admin action is required.
      adminActionRequired = true;
      messages.add(getMessage(MSGID_CONFIG_ACCTNOTHANDLER_CLASS_ACTION_REQUIRED,
                              String.valueOf(oldClassName),
                              String.valueOf(className),
                              String.valueOf(configEntryDN)));
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
      loadAndInstallNotificationHandler (newClassName, configuration);
    }
    if (needsEnabled)
    catch (InitializationException ie)
    {
      try
      {
        Class handlerClass = DirectoryServer.loadClass(className);
        handler = (AccountStatusNotificationHandler) handlerClass.newInstance();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS;
        messages.add(getMessage(msgID, className,
                                String.valueOf(configEntryDN),
                                String.valueOf(e)));
        resultCode = DirectoryServer.getServerErrorResultCode();
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      try
      {
        handler.initializeStatusNotificationHandler(configEntry);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INITIALIZATION_FAILED;
        messages.add(getMessage(msgID, className,
                                String.valueOf(configEntryDN),
                                String.valueOf(e)));
        resultCode = DirectoryServer.getServerErrorResultCode();
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      notificationHandlers.put(configEntryDN, handler);
      DirectoryServer.registerAccountStatusNotificationHandler(configEntryDN,
                                                               handler);
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
      changeResult.addMessage (ie.getMessage());
      changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
      return changeResult;
    }
    // If we've gotten here, then there haven't been any changes to anything
    // that we care about.
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return changeResult;
  }
  /**
   * Indicates whether the configuration entry that will result from a proposed
   * add is acceptable to this add listener.
   *
   * @param  configEntry         The configuration entry that will result from
   *                             the requested add.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed entry is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry contains an acceptable
   *          configuration, or <CODE>false</CODE> if it does not.
   * {@inheritDoc}
   */
  public boolean configAddIsAcceptable(ConfigEntry configEntry,
                                       StringBuilder unacceptableReason)
  public boolean isConfigurationAddAcceptable(
      AccountStatusNotificationHandlerCfg configuration,
      List<String> unacceptableReasons
      )
  {
    // returned status -- all is fine by default
    boolean status = true;
    // Make sure that no entry already exists with the specified DN.
    DN configEntryDN = configEntry.getDN();
    DN configEntryDN = configuration.dn();
    if (notificationHandlers.containsKey(configEntryDN))
    {
      int    msgID   = MSGID_CONFIG_ACCTNOTHANDLER_EXISTS;
      String message = getMessage(msgID, String.valueOf(configEntryDN));
      unacceptableReason.append(message);
      return false;
      unacceptableReasons.add (message);
      status = false;
    }
    // Make sure that the entry has an appropriate objectclass for an account
    // status notification handler.
    if (! configEntry.hasObjectClass(OC_ACCT_NOTIFICATION_HANDLER))
    // If configuration is enabled then check that notification class
    // can be instantiated.
    else if (configuration.isEnabled())
    {
      int    msgID   = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_OBJECTCLASS;
      String message = getMessage(msgID, configEntry.getDN().toString());
      unacceptableReason.append(message);
      return false;
    }
    // Make sure that the entry specifies the handler class.
    StringConfigAttribute classNameAttr;
    try
    {
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_CLASS_NAME;
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_CLASS,
                                     getMessage(msgID), true, false, true);
      classNameAttr = (StringConfigAttribute)
                      configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      // Get the name of the class and make sure we can instantiate it as
      // an entry cache.
      String className = configuration.getNotificationHandlerClass();
      try
      {
        msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_CLASS_NAME;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
        // Load the class but don't initialize it.
        loadNotificationHandler (className, null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      catch (InitializationException ie)
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    Class handlerClass;
    try
    {
      handlerClass = DirectoryServer.loadClass(classNameAttr.pendingValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    AccountStatusNotificationHandler handler;
    try
    {
      handler = (AccountStatusNotificationHandler) handlerClass.newInstance();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS;
      String message = getMessage(msgID, handlerClass.getName(),
                                  String.valueOf(configEntryDN),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // If the notofication handler is a configurable component, then make sure
    // that its configuration is valid.
    if (handler instanceof ConfigurableComponent)
    {
      ConfigurableComponent cc = (ConfigurableComponent) handler;
      LinkedList<String> errorMessages = new LinkedList<String>();
      if (! cc.hasAcceptableConfiguration(configEntry, errorMessages))
      {
        if (errorMessages.isEmpty())
        {
          int msgID = MSGID_CONFIG_ACCTNOTHANDLER_UNACCEPTABLE_CONFIG;
          unacceptableReason.append(getMessage(msgID,
                                               String.valueOf(configEntryDN)));
        }
        else
        {
          Iterator<String> iterator = errorMessages.iterator();
          unacceptableReason.append(iterator.next());
          while (iterator.hasNext())
          {
            unacceptableReason.append("  ");
            unacceptableReason.append(iterator.next());
          }
        }
        return false;
        unacceptableReasons.add (ie.getMessage());
        status = false;
      }
    }
    // See if this notification handler should be enabled.
    BooleanConfigAttribute enabledAttr;
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_ENABLED,
                    getMessage(MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      {
        int msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_ENABLED_ATTR;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_ENABLED_VALUE;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // If we've gotten here then the notification handler entry appears to be
    // acceptable.
    return true;
    return status;
  }
  /**
   * Attempts to apply a new configuration based on the provided added entry.
   *
   * @param  configEntry  The new configuration entry that contains the
   *                      configuration to apply.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry)
  public ConfigChangeResult applyConfigurationAdd(
      AccountStatusNotificationHandlerCfg configuration
      )
  {
    DN                configEntryDN       = configEntry.getDN();
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    // Register a change listener with it so we can be notified of changes
    // to it over time.
    configuration.addChangeListener(this);
    // Make sure that the entry has an appropriate objectclass for an account
    // status notification handler.
    if (! configEntry.hasObjectClass(OC_ACCT_NOTIFICATION_HANDLER))
    if (configuration.isEnabled())
    {
      int    msgID   = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_OBJECTCLASS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
      resultCode = ResultCode.UNWILLING_TO_PERFORM;
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // See if this notification handler should be enabled or disabled.
    BooleanConfigAttribute enabledAttr;
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_ENABLED,
                    getMessage(MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      // Instantiate the class as an entry cache and initialize it.
      String className = configuration.getNotificationHandlerClass();
      try
      {
        // The attribute doesn't exist, so it will be disabled by default.
        int msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_ENABLED_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.SUCCESS;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
        loadAndInstallNotificationHandler (className, configuration);
      }
      else if (! enabledAttr.activeValue())
      catch (InitializationException ie)
      {
        // It is explicitly configured as disabled, so we don't need to do
        // anything.
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
        changeResult.addMessage (ie.getMessage());
        changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
        return changeResult;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_ENABLED_VALUE;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Make sure that the entry specifies the handler class name.
    String className;
    try
    {
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_DESCRIPTION_CLASS_NAME;
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_ACCT_NOTIFICATION_HANDLER_CLASS,
                                     getMessage(msgID), true, false, true);
      StringConfigAttribute classNameAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      {
        msgID = MSGID_CONFIG_ACCTNOTHANDLER_NO_CLASS_NAME;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      className = classNameAttr.pendingValue();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS_NAME;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Load and initialize the notificationhandler class, and register it with
    // the Directory Server.
    AccountStatusNotificationHandler handler;
    try
    {
      Class handlerClass = DirectoryServer.loadClass(className);
      handler = (AccountStatusNotificationHandler) handlerClass.newInstance();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INVALID_CLASS;
      messages.add(getMessage(msgID, className, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    try
    {
      handler.initializeStatusNotificationHandler(configEntry);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INITIALIZATION_FAILED;
      messages.add(getMessage(msgID, className, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    notificationHandlers.put(configEntryDN, handler);
    DirectoryServer.registerAccountStatusNotificationHandler(configEntryDN,
                                                             handler);
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return changeResult;
  }
  /**
   * Indicates whether it is acceptable to remove the provided configuration
   * entry.
   *
   * @param  configEntry         The configuration entry that will be removed
   *                             from the configuration.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed delete is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry may be removed from the
   *          configuration, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean configDeleteIsAcceptable(ConfigEntry configEntry,
                                          StringBuilder unacceptableReason)
  public boolean isConfigurationDeleteAcceptable(
      AccountStatusNotificationHandlerCfg configuration,
      List<String> unacceptableReasons
      )
  {
    // A delete should always be acceptable, so just return true.
    return true;
@@ -940,33 +333,145 @@
  /**
   * Attempts to apply a new configuration based on the provided deleted entry.
   *
   * @param  configEntry  The new configuration entry that has been deleted.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry)
  public ConfigChangeResult applyConfigurationDelete(
      AccountStatusNotificationHandlerCfg configuration
      )
  {
    DN         configEntryDN       = configEntry.getDN();
    ResultCode resultCode          = ResultCode.SUCCESS;
    boolean    adminActionRequired = false;
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    uninstallNotificationHandler (configuration.dn());
    return changeResult;
  }
    // See if the entry is registered as an account status notification handler.
    // If so, deregister it and stop the handler.
  /**
   * Loads the specified class, instantiates it as a notification handler,
   * and optionally initializes that instance. Any initialized notification
   * handler is registered in the server.
   *
   * @param  className      The fully-qualified name of the notification handler
   *                        class to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the
   *                        notification handler, or {@code null} if the
   *                        notification handler should not be initialized.
   *
   * @throws  InitializationException  If a problem occurred while attempting
   *                                   to initialize the notification handler.
   */
  private void loadAndInstallNotificationHandler(
       String className,
       AccountStatusNotificationHandlerCfg configuration
       )
       throws InitializationException
  {
    // Load the notification handler class...
    AccountStatusNotificationHandler
        <? extends AccountStatusNotificationHandlerCfg> handlerClass;
    handlerClass = loadNotificationHandler (className, configuration);
    // ... and install the entry cache in the server.
    DN configEntryDN = configuration.dn();
    notificationHandlers.put (configEntryDN, handlerClass);
    DirectoryServer.registerAccountStatusNotificationHandler(
        configEntryDN,
        handlerClass
        );
  }
  /**
   * Loads the specified class, instantiates it as a notification handler,
   * and optionally initializes that instance.
   *
   * @param  className      The fully-qualified name of the notification handler
   *                        class to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the
   *                        notification handler, or {@code null} if the
   *                        notification handler should not be initialized.
   *
   * @return  The possibly initialized notification handler.
   *
   * @throws  InitializationException  If a problem occurred while attempting
   *                                   to initialize the notification handler.
   */
  private
    AccountStatusNotificationHandler
       <? extends AccountStatusNotificationHandlerCfg>
    loadNotificationHandler(
       String className,
       AccountStatusNotificationHandlerCfg configuration
       )
       throws InitializationException
  {
    try
    {
      AccountStatusNotificationHandlerCfgDefn definition;
      ClassPropertyDefinition propertyDefinition;
      Class<? extends AccountStatusNotificationHandler> handlerClass;
      AccountStatusNotificationHandler
         <? extends AccountStatusNotificationHandlerCfg> notificationHandler;
      definition = AccountStatusNotificationHandlerCfgDefn.getInstance();
      propertyDefinition =
          definition.getNotificationHandlerClassPropertyDefinition();
      handlerClass = propertyDefinition.loadClass(
          className,
          AccountStatusNotificationHandler.class
          );
      notificationHandler =
        (AccountStatusNotificationHandler
            <? extends AccountStatusNotificationHandlerCfg>)
        handlerClass.newInstance();
      if (configuration != null)
      {
        Method method = notificationHandler.getClass().getMethod(
            "initializeStatusNotificationHandler",
            configuration.definition().getServerConfigurationClass()
            );
        method.invoke(notificationHandler, configuration);
      }
      return notificationHandler;
    }
    catch (Exception e)
    {
      int msgID = MSGID_CONFIG_ACCTNOTHANDLER_INITIALIZATION_FAILED;
      String message = getMessage(
          msgID, className,
          String.valueOf(configuration.dn()),
          stackTraceToSingleLineString(e)
          );
      throw new InitializationException(msgID, message, e);
    }
  }
  /**
   * Remove a notification handler that has been installed in the server.
   *
   * @param configEntryDN  the DN of the configuration enry associated to
   *                       the notification handler to remove
   */
  private void uninstallNotificationHandler(
      DN configEntryDN
      )
  {
    AccountStatusNotificationHandler handler =
         notificationHandlers.remove(configEntryDN);
        notificationHandlers.remove (configEntryDN);
    if (handler != null)
    {
      DirectoryServer.deregisterAccountStatusNotificationHandler(configEntryDN);
      DirectoryServer.deregisterAccountStatusNotificationHandler (
          configEntryDN
          );
      handler.finalizeStatusNotificationHandler();
    }
    return new ConfigChangeResult(resultCode, adminActionRequired);
  }
}
opends/src/server/org/opends/server/core/EntryCacheConfigManager.java
@@ -28,24 +28,19 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.opends.server.api.ConfigAddListener;
import org.opends.server.api.ConfigChangeListener;
import org.opends.server.api.ConfigDeleteListener;
import org.opends.server.api.EntryCache;
import org.opends.server.config.BooleanConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.StringConfigAttribute;
import org.opends.server.extensions.DefaultEntryCache;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import org.opends.server.config.ConfigException;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.DebugLogLevel;
@@ -55,6 +50,16 @@
import static org.opends.server.util.StaticUtils.*;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.server.EntryCacheCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.admin.std.meta.EntryCacheCfgDefn;
/**
 * This class defines a utility that will be used to manage the configuration
@@ -62,9 +67,17 @@
 * defined, but if it is absent or disabled, then a default cache will be used.
 */
public class EntryCacheConfigManager
       implements ConfigChangeListener, ConfigAddListener, ConfigDeleteListener
       implements
          ConfigurationChangeListener <EntryCacheCfg>,
          ConfigurationAddListener    <EntryCacheCfg>,
          ConfigurationDeleteListener <EntryCacheCfg>
{
  // The current entry cache registered in the server
  private EntryCache _entryCache = null;
  // The default entry cache to use when no entry cache has been configured
  // or when the configured entry cache could not be initialized.
  private EntryCache _defaultEntryCache = null;
  /**
@@ -76,19 +89,36 @@
  }
  /**
   * Initializes the configuration associated with the Directory Server entry
   * cache.  This should only be called at Directory Server startup.  If an
   * error occurs, then a message will be logged and the default entry cache
   * will be installed.
   *
   * @throws  ConfigException  If a configuration problem causes the entry
   *                           cache initialization process to fail.
   *
   * @throws  InitializationException  If a problem occurs while trying to
   *                                   install the default entry cache.
   */
  public void initializeEntryCache()
         throws InitializationException
         throws ConfigException, InitializationException
  {
    // Get the root configuration object.
    ServerManagementContext managementContext =
      ServerManagementContext.getInstance();
    RootCfg rootConfiguration =
      managementContext.getRootConfiguration();
    // Register as an add and delete listener with the root configuration so we
    // can be notified if any entry cache entry is added or removed.
    // If entry cache configuration is using a one-to-zero-or-one relation
    // then uncomment the lines below (see issue #1558).
    /*
    // rootConfiguration.addEntryCacheAddListener(this);
    // rootConfiguration.addEntryCacheDeleteListener(this);
    */
    // First, install a default entry cache so that there will be one even if
    // we encounter a problem later.
    try
@@ -96,6 +126,7 @@
      DefaultEntryCache defaultCache = new DefaultEntryCache();
      defaultCache.initializeEntryCache(null);
      DirectoryServer.setEntryCache(defaultCache);
      _defaultEntryCache = defaultCache;
    }
    catch (Exception e)
    {
@@ -109,377 +140,336 @@
      throw new InitializationException(msgID, message, e);
    }
    // If the entry cache configuration is not present then keep the
    // default entry cache already installed.
    // If entry cache configuration is using a one-to-zero-or-one relation
    // then uncomment the lines below (see issue #1558).
    /*
    //    if (!rootConfiguration.hasEntryCache())
    //    {
    //      logError(
    //          ErrorLogCategory.CONFIGURATION,
    //          ErrorLogSeverity.SEVERE_WARNING,
    //          MSGID_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY
    //          );
    //      return;
    //    }
    */
    // Get the entry cache configuration entry.  If it is not present, then
    // register an add listener and install the default cache.
    DN configEntryDN;
    ConfigEntry configEntry;
    try
    // Get the entry cache configuration.
    EntryCacheCfg configuration = rootConfiguration.getEntryCache();
    // At this point, we have a configuration entry. Register a change
    // listener with it so we can be notified of changes to it over time.
    configuration.addChangeListener(this);
    // Initialize the entry cache.
    if (configuration.isEnabled())
    {
      configEntryDN = DN.decode(DN_ENTRY_CACHE_CONFIG);
      configEntry   = DirectoryServer.getConfigEntry(configEntryDN);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_CONFIG_ENTRYCACHE_CANNOT_GET_CONFIG_ENTRY,
               stackTraceToSingleLineString(e));
      return;
    }
    if (configEntry == null)
    {
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_WARNING,
               MSGID_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY);
      // Load the entry cache implementation class and install the entry
      // cache with the server.
      String className = configuration.getEntryCacheClass();
      try
      {
        ConfigEntry parentEntry = DirectoryServer
            .getConfigEntry(configEntryDN.getParentDNInSuffix());
        if (parentEntry != null)
        {
          parentEntry.registerAddListener(this);
        }
        loadAndInstallEntryCache (className, configuration);
      }
      catch (Exception e)
      catch (InitializationException ie)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 MSGID_CONFIG_ENTRYCACHE_CANNOT_REGISTER_ADD_LISTENER,
                 stackTraceToSingleLineString(e));
        logError(
            ErrorLogCategory.CONFIGURATION,
            ErrorLogSeverity.SEVERE_ERROR,
            ie.getMessage(),
            ie.getMessageID());
      }
    }
  }
      return;
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      EntryCacheCfg configuration,
      List<String>  unacceptableReasons
      )
  {
    // returned status -- all is fine by default
    boolean status = true;
    if (configuration.isEnabled())
    {
      // Get the name of the class and make sure we can instantiate it as an
      // entry cache.
      String className = configuration.getEntryCacheClass();
      try
      {
        // Load the class but don't initialize it.
        loadEntryCache(className, null);
      }
      catch (InitializationException ie)
      {
        unacceptableReasons.add(ie.getMessage());
        status = false;
      }
    }
    return status;
  }
    // At this point, we have a configuration entry.  Register a change listener
    // with it so we can be notified of changes to it over time.  We will also
    // want to register a delete listener with its parent to allow us to
    // determine if the entry is deleted.
    configEntry.registerChangeListener(this);
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      EntryCacheCfg configuration
      )
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    // If the new configuration has the entry cache disabled, then install
    // the default entry cache with the server.
    if (! configuration.isEnabled())
    {
      DirectoryServer.setEntryCache (_defaultEntryCache);
      // If an entry cache was installed then clean it.
      if (_entryCache != null)
      {
        _entryCache.finalizeEntryCache();
        _entryCache = null;
      }
      return changeResult;
    }
    // At this point, new configuration is enabled...
    // If the current entry cache is already enabled then we don't do
    // anything unless the class has changed in which case we should
    // indicate that administrative action is required.
    String newClassName = configuration.getEntryCacheClass();
    if (_entryCache !=null)
    {
      String curClassName = _entryCache.getClass().getName();
      boolean classIsNew = (! newClassName.equals (curClassName));
      if (classIsNew)
      {
        changeResult.setAdminActionRequired (true);
      }
      return changeResult;
    }
    // New entry cache is enabled and there were no previous one.
    // Instantiate the new class and initalize it.
    try
    {
      DN parentDN = configEntryDN.getParentDNInSuffix();
      ConfigEntry parentEntry = DirectoryServer.getConfigEntry(parentDN);
      if (parentEntry != null)
      loadAndInstallEntryCache (newClassName, configuration);
    }
    catch (InitializationException ie)
    {
      changeResult.addMessage (ie.getMessage());
      changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
      return changeResult;
    }
    return changeResult;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationAddAcceptable(
      EntryCacheCfg configuration,
      List<String>  unacceptableReasons
      )
  {
    // returned status -- all is fine by default
    boolean status = true;
    if (configuration.isEnabled())
    {
      // Get the name of the class and make sure we can instantiate it as
      // an entry cache.
      String className = configuration.getEntryCacheClass();
      try
      {
        parentEntry.registerDeleteListener(this);
        // Load the class but don't initialize it.
        loadEntryCache(className, null);
      }
      catch (InitializationException ie)
      {
        unacceptableReasons.add (ie.getMessage());
        status = false;
      }
    }
    catch (Exception e)
    return status;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(
      EntryCacheCfg configuration
      )
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    // Register a change listener with it so we can be notified of changes
    // to it over time.
    configuration.addChangeListener(this);
    if (configuration.isEnabled())
    {
      if (debugEnabled())
      // Instantiate the class as an entry cache and initialize it.
      String className = configuration.getEntryCacheClass();
      try
      {
        debugCaught(DebugLogLevel.ERROR, e);
        loadAndInstallEntryCache (className, configuration);
      }
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_WARNING,
               MSGID_CONFIG_ENTRYCACHE_CANNOT_REGISTER_DELETE_LISTENER,
               stackTraceToSingleLineString(e));
    }
    // See if the entry indicates whether the cache should be enabled.
    int msgID = MSGID_CONFIG_ENTRYCACHE_DESCRIPTION_CACHE_ENABLED;
    BooleanConfigAttribute enabledStub =
         new BooleanConfigAttribute(ATTR_ENTRYCACHE_ENABLED, getMessage(msgID),
                                    false);
    try
    {
      BooleanConfigAttribute enabledAttr =
           (BooleanConfigAttribute)
           configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      catch (InitializationException ie)
      {
        // The attribute is not present, so the entry cache will be disabled.
        // Log a warning message and return.
        logError(ErrorLogCategory.CONFIGURATION,
                 ErrorLogSeverity.SEVERE_WARNING,
                 MSGID_CONFIG_ENTRYCACHE_NO_ENABLED_ATTR);
        return;
      }
      else if (! enabledAttr.activeValue())
      {
        // The entry cache is explicitly disabled.  Log a mild warning and
        // return.
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.MILD_WARNING,
                 MSGID_CONFIG_ENTRYCACHE_DISABLED);
        return;
        changeResult.addMessage (ie.getMessage());
        changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
        return changeResult;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_CONFIG_ENTRYCACHE_UNABLE_TO_DETERMINE_ENABLED_STATE,
               stackTraceToSingleLineString(e));
      return;
    return changeResult;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationDeleteAcceptable(
      EntryCacheCfg configuration,
      List<String>  unacceptableReasons
      )
  {
    // NYI
    // If we've gotten to this point, then it is acceptable as far as we are
    // concerned.  If it is unacceptable according to the configuration, then
    // the entry cache itself will make that determination.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(
      EntryCacheCfg configuration
      )
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    // If the entry cache was installed then replace it with the
    // default entry cache, and clean it.
    if (_entryCache != null)
    {
      DirectoryServer.setEntryCache (_defaultEntryCache);
      _entryCache.finalizeEntryCache();
      _entryCache = null;
    }
    // See if it specifies the class name for the entry cache implementation.
    String className;
    msgID = MSGID_CONFIG_ENTRYCACHE_DESCRIPTION_CACHE_CLASS;
    StringConfigAttribute classStub =
         new StringConfigAttribute(ATTR_ENTRYCACHE_CLASS, getMessage(msgID),
                                   true, false, false);
    try
    {
      StringConfigAttribute classAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(classStub);
      if (classAttr == null)
      {
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 MSGID_CONFIG_ENTRYCACHE_NO_CLASS_ATTR);
        return;
      }
      else
      {
        className = classAttr.activeValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_CONFIG_ENTRYCACHE_CANNOT_DETERMINE_CLASS,
               stackTraceToSingleLineString(e));
      return;
    }
    return changeResult;
  }
    // Try to load the class and instantiate it as an entry cache.
    Class cacheClass;
    try
    {
      cacheClass = DirectoryServer.loadClass(className);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
  /**
   * Loads the specified class, instantiates it as an entry cache,
   * and optionally initializes that instance. Any initialize entry
   * cache is registered in the server.
   *
   * @param  className      The fully-qualified name of the entry cache
   *                        class to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the
   *                        entry cache, or {@code null} if the
   *                        entry cache should not be initialized.
   *
   * @throws  InitializationException  If a problem occurred while attempting
   *                                   to initialize the entry cache.
   */
  private void loadAndInstallEntryCache(
    String        className,
    EntryCacheCfg configuration
    )
    throws InitializationException
  {
    // Load the entry cache class...
    EntryCache entryCache = loadEntryCache (className, configuration);
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_CONFIG_ENTRYCACHE_CANNOT_LOAD_CLASS,
               String.valueOf(className), stackTraceToSingleLineString(e));
      return;
    }
    EntryCache entryCache;
    try
    {
      entryCache = (EntryCache) cacheClass.newInstance();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_CONFIG_ENTRYCACHE_CANNOT_INSTANTIATE_CLASS,
               String.valueOf(className), stackTraceToSingleLineString(e));
      return;
    }
    // Try to initialize the cache with the contents of the configuration entry.
    try
    {
      entryCache.initializeEntryCache(configEntry);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_CONFIG_ENTRYCACHE_CANNOT_INITIALIZE_CACHE,
               String.valueOf(className), stackTraceToSingleLineString(e));
      return;
    }
    // Install the new cache with the server.  We don't need to do anything to
    // get rid of the previous default cache since it doesn't consume any
    // resources.
    // ... and install the entry cache in the server.
    DirectoryServer.setEntryCache(entryCache);
    _entryCache = entryCache;
  }
  /**
   * Indicates whether the configuration entry that will result from a proposed
   * modification is acceptable to this change listener.
   * Loads the specified class, instantiates it as an entry cache,
   * and optionally initializes that instance.
   *
   * @param  configEntry         The configuration entry that will result from
   *                             the requested update.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed change is not acceptable.
   * @param  className      The fully-qualified name of the entry cache
   *                        class to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the
   *                        entry cache, or {@code null} if the
   *                        entry cache should not be initialized.
   *
   * @return  <CODE>true</CODE> if the proposed entry contains an acceptable
   *          configuration, or <CODE>false</CODE> if it does not.
   * @return  The possibly initialized entry cache.
   *
   * @throws  InitializationException  If a problem occurred while attempting
   *                                   to initialize the entry cache.
   */
  public boolean configChangeIsAcceptable(ConfigEntry configEntry,
                                          StringBuilder unacceptableReason)
  private EntryCache<? extends EntryCacheCfg> loadEntryCache(
    String        className,
    EntryCacheCfg configuration
    )
    throws InitializationException
  {
    // NYI
    try
    {
      EntryCacheCfgDefn                   definition;
      ClassPropertyDefinition             propertyDefinition;
      Class<? extends EntryCache>         cacheClass;
      EntryCache<? extends EntryCacheCfg> cache;
      definition = EntryCacheCfgDefn.getInstance();
      propertyDefinition = definition.getEntryCacheClassPropertyDefinition();
      cacheClass = propertyDefinition.loadClass(className, EntryCache.class);
      cache = (EntryCache<? extends EntryCacheCfg>) cacheClass.newInstance();
    // If we've gotten to this point, then it is acceptable as far as we are
    // concerned.  If it is unacceptable according to the configuration, then
    // the entry cache itself will make that determination.
    return true;
      if (configuration != null)
      {
        Method method = cache.getClass().getMethod(
            "initializeEntryCache",
            configuration.definition().getServerConfigurationClass()
            );
        method.invoke(cache, configuration);
      }
      return cache;
    }
    catch (Exception e)
    {
      int msgID = MSGID_CONFIG_ENTRYCACHE_CANNOT_INITIALIZE_CACHE;
      String message = getMessage(
          msgID, className,
          String.valueOf(configuration.dn()),
          stackTraceToSingleLineString(e)
          );
      throw new InitializationException(msgID, message, e);
    }
  }
  /**
   * Attempts to apply a new configuration to this Directory Server component
   * based on the provided changed entry.
   *
   * @param  configEntry  The configuration entry that containing the updated
   *                      configuration for this component.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   */
  public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // NYI
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * Indicates whether the configuration entry that will result from a proposed
   * add is acceptable to this add listener.
   *
   * @param  configEntry         The configuration entry that will result from
   *                             the requested add.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed entry is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry contains an acceptable
   *          configuration, or <CODE>false</CODE> if it does not.
   */
  public boolean configAddIsAcceptable(ConfigEntry configEntry,
                                       StringBuilder unacceptableReason)
  {
    // NYI
    // If we've gotten to this point, then it is acceptable as far as we are
    // concerned.  If it is unacceptable according to the configuration, then
    // the entry cache itself will make that determination.
    return true;
  }
  /**
   * Attempts to apply a new configuration based on the provided added entry.
   *
   * @param  configEntry  The new configuration entry that contains the
   *                      configuration to apply.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   */
  public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // NYI
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * Indicates whether it is acceptable to remove the provided configuration
   * entry.
   *
   * @param  configEntry         The configuration entry that will be removed
   *                             from the configuration.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed delete is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry may be removed from the
   *          configuration, or <CODE>false</CODE> if not.
   */
  public boolean configDeleteIsAcceptable(ConfigEntry configEntry,
                                          StringBuilder unacceptableReason)
  {
    // NYI
    // If we've gotten to this point, then it is acceptable as far as we are
    // concerned.  If it is unacceptable according to the configuration, then
    // the entry cache itself will make that determination.
    return true;
  }
  /**
   * Attempts to apply a new configuration based on the provided deleted entry.
   *
   * @param  configEntry  The new configuration entry that has been deleted.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   */
  public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // NYI
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/core/PasswordStorageSchemeConfigManager.java
@@ -28,39 +28,30 @@
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.server.api.ConfigAddListener;
import org.opends.server.api.ConfigChangeListener;
import org.opends.server.api.ConfigDeleteListener;
import org.opends.server.api.ConfigHandler;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.BooleanConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.StringConfigAttribute;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
@@ -70,26 +61,21 @@
 * removals, or modifications to any schemes while the server is running.
 */
public class PasswordStorageSchemeConfigManager
       implements ConfigChangeListener, ConfigAddListener, ConfigDeleteListener
       implements
          ConfigurationChangeListener <PasswordStorageSchemeCfg>,
          ConfigurationAddListener    <PasswordStorageSchemeCfg>,
          ConfigurationDeleteListener <PasswordStorageSchemeCfg>
{
  // A mapping between the DNs of the config entries and the associated password
  // storage schemes.
  private ConcurrentHashMap<DN,PasswordStorageScheme> storageSchemes;
  // The configuration handler for the Directory Server.
  private ConfigHandler configHandler;
  /**
   * Creates a new instance of this password storage scheme config manager.
   */
  public PasswordStorageSchemeConfigManager()
  {
    configHandler  = DirectoryServer.getConfigHandler();
    storageSchemes = new ConcurrentHashMap<DN,PasswordStorageScheme>();
  }
@@ -110,99 +96,35 @@
  public void initializePasswordStorageSchemes()
         throws ConfigException, InitializationException
  {
    // First, get the configuration base entry.
    ConfigEntry baseEntry;
    try
    // Get the root configuration object.
    ServerManagementContext managementContext =
      ServerManagementContext.getInstance();
    RootCfg rootConfiguration =
      managementContext.getRootConfiguration();
    // Register as an add and delete listener with the root configuration so we
    // can be notified if any entry cache entry is added or removed.
    rootConfiguration.addPasswordStorageSchemeAddListener (this);
    rootConfiguration.addPasswordStorageSchemeDeleteListener (this);
    // Initialize existing password storage schemes.
    for (String schemeName: rootConfiguration.listPasswordStorageSchemes())
    {
      DN schemeBase = DN.decode(DN_PWSCHEME_CONFIG_BASE);
      baseEntry = configHandler.getConfigEntry(schemeBase);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      // Get the password storage scheme's configuration.
      PasswordStorageSchemeCfg config =
        rootConfiguration.getPasswordStorageScheme (schemeName);
      // Register as a change listener for this password storage scheme
      // entry so that we will be notified of any changes that may be
      // made to it.
      config.addChangeListener (this);
      // Ignore this password storage scheme if it is disabled.
      if (config.isEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int    msgID   = MSGID_CONFIG_PWSCHEME_CANNOT_GET_BASE;
      String message = getMessage(msgID, String.valueOf(e));
      throw new ConfigException(msgID, message, e);
    }
    if (baseEntry == null)
    {
      // The password storage scheme base entry does not exist.  This is not
      // acceptable, so throw an exception.
      int    msgID   = MSGID_CONFIG_PWSCHEME_BASE_DOES_NOT_EXIST;
      String message = getMessage(msgID);
      throw new ConfigException(msgID, message);
    }
    // Register add and delete listeners with the storage scheme base entry.  We
    // don't care about modifications to it.
    baseEntry.registerAddListener(this);
    baseEntry.registerDeleteListener(this);
    // See if the base entry has any children.  If not, then we don't need to do
    // anything else.
    if (! baseEntry.hasChildren())
    {
      return;
    }
    // Iterate through the child entries and process them as password storage
    // scheme configuration entries.
    for (ConfigEntry childEntry : baseEntry.getChildren().values())
    {
      childEntry.registerChangeListener(this);
      StringBuilder unacceptableReason = new StringBuilder();
      if (! configAddIsAcceptable(childEntry, unacceptableReason))
      {
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 MSGID_CONFIG_PWSCHEME_ENTRY_UNACCEPTABLE,
                 childEntry.getDN().toString(), unacceptableReason.toString());
        continue;
      }
      try
      {
        ConfigChangeResult result = applyConfigurationAdd(childEntry);
        if (result.getResultCode() != ResultCode.SUCCESS)
        {
          StringBuilder buffer = new StringBuilder();
          List<String> resultMessages = result.getMessages();
          if ((resultMessages == null) || (resultMessages.isEmpty()))
          {
            buffer.append(getMessage(MSGID_CONFIG_UNKNOWN_UNACCEPTABLE_REASON));
          }
          else
          {
            Iterator<String> iterator = resultMessages.iterator();
            buffer.append(iterator.next());
            while (iterator.hasNext())
            {
              buffer.append(EOL);
              buffer.append(iterator.next());
            }
          }
          logError(ErrorLogCategory.CONFIGURATION,
                   ErrorLogSeverity.SEVERE_ERROR,
                   MSGID_CONFIG_PWSCHEME_CANNOT_CREATE_SCHEME,
                   childEntry.getDN().toString(), buffer.toString());
        }
      }
      catch (Exception e)
      {
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 MSGID_CONFIG_PWSCHEME_CANNOT_CREATE_SCHEME,
                 childEntry.getDN().toString(), String.valueOf(e));
        // Load the password storage scheme implementation class.
        String className = config.getSchemeClass();
        loadAndInstallPasswordStorageScheme (className, config);
      }
    }
  }
@@ -210,720 +132,192 @@
  /**
   * Indicates whether the configuration entry that will result from a proposed
   * modification is acceptable to this change listener.
   *
   * @param  configEntry         The configuration entry that will result from
   *                             the requested update.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed change is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry contains an acceptable
   *          configuration, or <CODE>false</CODE> if it does not.
   * {@inheritDoc}
   */
  public boolean configChangeIsAcceptable(ConfigEntry configEntry,
                                          StringBuilder unacceptableReason)
  public boolean isConfigurationChangeAcceptable(
      PasswordStorageSchemeCfg configuration,
      List<String>             unacceptableReasons
      )
  {
    // Make sure that the entry has an appropriate objectclass for a password
    // storage scheme.
    if (! configEntry.hasObjectClass(OC_PASSWORD_STORAGE_SCHEME))
    // returned status -- all is fine by default
    boolean status = true;
    if (configuration.isEnabled())
    {
      int    msgID   = MSGID_CONFIG_PWSCHEME_INVALID_OBJECTCLASS;
      String message = getMessage(msgID, configEntry.getDN().toString());
      unacceptableReason.append(message);
      return false;
    }
    // Make sure that the entry specifies the storage scheme class name.
    StringConfigAttribute classNameAttr;
    try
    {
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_PWSCHEME_CLASS,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_CLASS_NAME),
                    true, false, true);
      classNameAttr = (StringConfigAttribute)
                      configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      // Get the name of the class and make sure we can instantiate it as
      // a password storage scheme.
      String className = configuration.getSchemeClass();
      try
      {
        int msgID = MSGID_CONFIG_PWSCHEME_NO_CLASS_NAME;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
        // Load the class but don't initialize it.
        loadPasswordStorageScheme (className, null);
      }
      catch (InitializationException ie)
      {
        unacceptableReasons.add(ie.getMessage());
        status = false;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    Class schemeClass;
    try
    {
      schemeClass = DirectoryServer.loadClass(classNameAttr.pendingValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    try
    {
      PasswordStorageScheme scheme =
           (PasswordStorageScheme) schemeClass.newInstance();
    }
    catch(Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS;
      String message = getMessage(msgID, schemeClass.getName(),
                                  String.valueOf(configEntry.getDN()),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // See if this password storage scheme should be enabled.
    BooleanConfigAttribute enabledAttr;
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_PWSCHEME_ENABLED,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      {
        int msgID = MSGID_CONFIG_PWSCHEME_NO_ENABLED_ATTR;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_ENABLED_VALUE;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // If we've gotten here then the password storage scheme entry appears to be
    // acceptable.
    return true;
    return status;
  }
  /**
   * Attempts to apply a new configuration to this Directory Server component
   * based on the provided changed entry.
   *
   * @param  configEntry  The configuration entry that containing the updated
   *                      configuration for this component.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry)
  public ConfigChangeResult applyConfigurationChange(
      PasswordStorageSchemeCfg configuration
      )
  {
    DN                configEntryDN       = configEntry.getDN();
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    // Get the configuration entry DN and the associated
    // password storage scheme class.
    DN configEntryDN = configuration.dn();
    PasswordStorageScheme storageScheme = storageSchemes.get(
        configEntryDN
        );
    // Make sure that the entry has an appropriate objectclass for a password
    // storage scheme.
    if (! configEntry.hasObjectClass(OC_PASSWORD_STORAGE_SCHEME))
    // If the new configuration has the password storage scheme disabled,
    // then remove it from the mapping list and clean it.
    if (! configuration.isEnabled())
    {
      int    msgID   = MSGID_CONFIG_PWSCHEME_INVALID_OBJECTCLASS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
      resultCode = ResultCode.UNWILLING_TO_PERFORM;
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
      if (storageScheme != null)
      {
        uninstallPasswordStorageScheme (configEntryDN);
      }
      return changeResult;
    }
    // At this point, new configuration is enabled...
    // If the current password storage scheme is already enabled then we
    // don't do anything unless the class has changed in which case we
    // should indicate that administrative action is required.
    String newClassName = configuration.getSchemeClass();
    if (storageScheme != null)
    {
      String curClassName = storageScheme.getClass().getName();
      boolean classIsNew = (! newClassName.equals (curClassName));
      if (classIsNew)
      {
        changeResult.setAdminActionRequired (true);
      }
      return changeResult;
    }
    // Get the corresponding password storage scheme if it is active.
    PasswordStorageScheme scheme = storageSchemes.get(configEntryDN);
    // See if this scheme should be enabled or disabled.
    boolean needsEnabled = false;
    BooleanConfigAttribute enabledAttr;
    // New entry cache is enabled and there were no previous one.
    // Instantiate the new class and initalize it.
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_PWSCHEME_ENABLED,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      {
        int msgID = MSGID_CONFIG_PWSCHEME_NO_ENABLED_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.UNWILLING_TO_PERFORM;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      if (enabledAttr.activeValue())
      {
        if (scheme == null)
        {
          needsEnabled = true;
        }
        else
        {
          // The scheme is already active, so no action is required.
        }
      }
      else
      {
        if (scheme == null)
        {
          // The scheme is already disabled, so no action is required and we
          // can short-circuit out of this processing.
          return new ConfigChangeResult(resultCode, adminActionRequired,
                                        messages);
        }
        else
        {
          // The scheme is active, so it needs to be disabled.  Do this and
          // return that we were successful.
          storageSchemes.remove(configEntryDN);
          scheme.finalizePasswordStorageScheme();
          String lowerName = toLowerCase(scheme.getStorageSchemeName());
          DirectoryServer.deregisterPasswordStorageScheme(lowerName);
          return new ConfigChangeResult(resultCode, adminActionRequired,
                                        messages);
        }
      }
      loadAndInstallPasswordStorageScheme (newClassName, configuration);
    }
    catch (Exception e)
    catch (InitializationException ie)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_ENABLED_VALUE;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
      changeResult.addMessage (ie.getMessage());
      changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
      return changeResult;
    }
    // Make sure that the entry specifies the storage scheme class name.  If it
    // has changed, then we will not try to dynamically apply it.
    String className;
    try
    {
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_PWSCHEME_CLASS,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_CLASS_NAME),
                    true, false, true);
      StringConfigAttribute classNameAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      {
        int msgID = MSGID_CONFIG_PWSCHEME_NO_CLASS_NAME;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      className = classNameAttr.pendingValue();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS_NAME;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    boolean classChanged = false;
    String  oldClassName = null;
    if (scheme != null)
    {
      oldClassName = scheme.getClass().getName();
      classChanged = (! className.equals(oldClassName));
    }
    if (classChanged)
    {
      // This will not be applied dynamically.  Add a message to the response
      // and indicate that admin action is required.
      adminActionRequired = true;
      messages.add(getMessage(MSGID_CONFIG_PWSCHEME_CLASS_ACTION_REQUIRED,
                              String.valueOf(oldClassName),
                              String.valueOf(className),
                              String.valueOf(configEntryDN)));
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    if (needsEnabled)
    {
      try
      {
        Class schemeClass = DirectoryServer.loadClass(className);
        scheme = (PasswordStorageScheme) schemeClass.newInstance();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS;
        messages.add(getMessage(msgID, className,
                                String.valueOf(configEntryDN),
                                String.valueOf(e)));
        resultCode = DirectoryServer.getServerErrorResultCode();
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      try
      {
        scheme.initializePasswordStorageScheme(configEntry);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        int msgID = MSGID_CONFIG_PWSCHEME_INITIALIZATION_FAILED;
        messages.add(getMessage(msgID, className,
                                String.valueOf(configEntryDN),
                                String.valueOf(e)));
        resultCode = DirectoryServer.getServerErrorResultCode();
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      storageSchemes.put(configEntryDN, scheme);
      DirectoryServer.registerPasswordStorageScheme(scheme);
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // If we've gotten here, then there haven't been any changes to anything
    // that we care about.
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return changeResult;
  }
  /**
   * Indicates whether the configuration entry that will result from a proposed
   * add is acceptable to this add listener.
   *
   * @param  configEntry         The configuration entry that will result from
   *                             the requested add.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed entry is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry contains an acceptable
   *          configuration, or <CODE>false</CODE> if it does not.
   * {@inheritDoc}
   */
  public boolean configAddIsAcceptable(ConfigEntry configEntry,
                                       StringBuilder unacceptableReason)
  public boolean isConfigurationAddAcceptable(
      PasswordStorageSchemeCfg configuration,
      List<String>             unacceptableReasons
      )
  {
    // returned status -- all is fine by default
    boolean status = true;
    // Make sure that no entry already exists with the specified DN.
    DN configEntryDN = configEntry.getDN();
    DN configEntryDN = configuration.dn();
    if (storageSchemes.containsKey(configEntryDN))
    {
      int    msgID   = MSGID_CONFIG_PWSCHEME_EXISTS;
      String message = getMessage(msgID, String.valueOf(configEntryDN));
      unacceptableReason.append(message);
      return false;
      unacceptableReasons.add (message);
      status = false;
    }
    // Make sure that the entry has an appropriate objectclass for a password
    // storage scheme.
    if (! configEntry.hasObjectClass(OC_PASSWORD_STORAGE_SCHEME))
    // If configuration is enabled then check that password storage scheme
    // class can be instantiated.
    else if (configuration.isEnabled())
    {
      int    msgID   = MSGID_CONFIG_PWSCHEME_INVALID_OBJECTCLASS;
      String message = getMessage(msgID, configEntry.getDN().toString());
      unacceptableReason.append(message);
      return false;
    }
    // Make sure that the entry specifies the password storage scheme class.
    StringConfigAttribute classNameAttr;
    try
    {
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_PWSCHEME_CLASS,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_CLASS_NAME),
                    true, false, true);
      classNameAttr = (StringConfigAttribute)
                      configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      // Get the name of the class and make sure we can instantiate it as
      // an entry cache.
      String className = configuration.getSchemeClass();
      try
      {
        int msgID = MSGID_CONFIG_PWSCHEME_NO_CLASS_NAME;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
        // Load the class but don't initialize it.
        loadPasswordStorageScheme (className, null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      catch (InitializationException ie)
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    Class schemeClass;
    try
    {
      schemeClass = DirectoryServer.loadClass(classNameAttr.pendingValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS_NAME;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    PasswordStorageScheme storageScheme;
    try
    {
      storageScheme = (PasswordStorageScheme) schemeClass.newInstance();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS;
      String message = getMessage(msgID, schemeClass.getName(),
                                  String.valueOf(configEntryDN),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // If the storage scheme is a configurable component, then make sure that
    // its configuration is valid.
    if (storageScheme instanceof ConfigurableComponent)
    {
      ConfigurableComponent cc = (ConfigurableComponent) storageScheme;
      LinkedList<String> errorMessages = new LinkedList<String>();
      if (! cc.hasAcceptableConfiguration(configEntry, errorMessages))
      {
        if (errorMessages.isEmpty())
        {
          int msgID = MSGID_CONFIG_PWSCHEME_UNACCEPTABLE_CONFIG;
          unacceptableReason.append(getMessage(msgID,
                                               String.valueOf(configEntryDN)));
        }
        else
        {
          Iterator<String> iterator = errorMessages.iterator();
          unacceptableReason.append(iterator.next());
          while (iterator.hasNext())
          {
            unacceptableReason.append("  ");
            unacceptableReason.append(iterator.next());
          }
        }
        return false;
        unacceptableReasons.add (ie.getMessage());
        status = false;
      }
    }
    // See if this storage scheme should be enabled.
    BooleanConfigAttribute enabledAttr;
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_PWSCHEME_ENABLED,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      {
        int msgID = MSGID_CONFIG_PWSCHEME_NO_ENABLED_ATTR;
        String message = getMessage(msgID, configEntry.getDN().toString());
        unacceptableReason.append(message);
        return false;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_ENABLED_VALUE;
      String message = getMessage(msgID, configEntry.getDN().toString(),
                                  String.valueOf(e));
      unacceptableReason.append(message);
      return false;
    }
    // If we've gotten here then the storage scheme entry appears to be
    // acceptable.
    return true;
    return status;
  }
  /**
   * Attempts to apply a new configuration based on the provided added entry.
   *
   * @param  configEntry  The new configuration entry that contains the
   *                      configuration to apply.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry)
  public ConfigChangeResult applyConfigurationAdd(
      PasswordStorageSchemeCfg configuration
      )
  {
    DN                configEntryDN       = configEntry.getDN();
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    // Register a change listener with it so we can be notified of changes
    // to it over time.
    configuration.addChangeListener(this);
    // Make sure that the entry has an appropriate objectclass for a password
    // storage scheme.
    if (! configEntry.hasObjectClass(OC_PASSWORD_STORAGE_SCHEME))
    if (configuration.isEnabled())
    {
      int    msgID   = MSGID_CONFIG_PWSCHEME_INVALID_OBJECTCLASS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
      resultCode = ResultCode.UNWILLING_TO_PERFORM;
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // See if this storage scheme should be enabled or disabled.
    BooleanConfigAttribute enabledAttr;
    try
    {
      BooleanConfigAttribute enabledStub =
           new BooleanConfigAttribute(ATTR_PWSCHEME_ENABLED,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_ENABLED),
                               false);
      enabledAttr = (BooleanConfigAttribute)
                    configEntry.getConfigAttribute(enabledStub);
      if (enabledAttr == null)
      // Instantiate the class as password storage scheme
      // and initialize it.
      String className = configuration.getSchemeClass();
      try
      {
        // The attribute doesn't exist, so it will be disabled by default.
        int msgID = MSGID_CONFIG_PWSCHEME_NO_ENABLED_ATTR;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.SUCCESS;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
        loadAndInstallPasswordStorageScheme (className, configuration);
      }
      else if (! enabledAttr.activeValue())
      catch (InitializationException ie)
      {
        // It is explicitly configured as disabled, so we don't need to do
        // anything.
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
        changeResult.addMessage (ie.getMessage());
        changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
        return changeResult;
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_ENABLED_VALUE;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Make sure that the entry specifies the storage scheme class name.
    String className;
    try
    {
      StringConfigAttribute classStub =
           new StringConfigAttribute(ATTR_PWSCHEME_CLASS,
                    getMessage(MSGID_CONFIG_PWSCHEME_DESCRIPTION_CLASS_NAME),
                    true, false, true);
      StringConfigAttribute classNameAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(classStub);
      if (classNameAttr == null)
      {
        int msgID = MSGID_CONFIG_PWSCHEME_NO_CLASS_NAME;
        messages.add(getMessage(msgID, String.valueOf(configEntryDN)));
        resultCode = ResultCode.OBJECTCLASS_VIOLATION;
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      className = classNameAttr.pendingValue();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS_NAME;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Load and initialize the storage scheme class, and register it with the
    // Directory Server.
    PasswordStorageScheme storageScheme;
    try
    {
      Class schemeClass = DirectoryServer.loadClass(className);
      storageScheme = (PasswordStorageScheme) schemeClass.newInstance();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INVALID_CLASS;
      messages.add(getMessage(msgID, className, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    try
    {
      storageScheme.initializePasswordStorageScheme(configEntry);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_CONFIG_PWSCHEME_INITIALIZATION_FAILED;
      messages.add(getMessage(msgID, className, String.valueOf(configEntryDN),
                              String.valueOf(e)));
      resultCode = DirectoryServer.getServerErrorResultCode();
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    storageSchemes.put(configEntryDN, storageScheme);
    DirectoryServer.registerPasswordStorageScheme(storageScheme);
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return changeResult;
  }
  /**
   * Indicates whether it is acceptable to remove the provided configuration
   * entry.
   *
   * @param  configEntry         The configuration entry that will be removed
   *                             from the configuration.
   * @param  unacceptableReason  A buffer to which this method can append a
   *                             human-readable message explaining why the
   *                             proposed delete is not acceptable.
   *
   * @return  <CODE>true</CODE> if the proposed entry may be removed from the
   *          configuration, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean configDeleteIsAcceptable(ConfigEntry configEntry,
                                          StringBuilder unacceptableReason)
  public boolean isConfigurationDeleteAcceptable(
      PasswordStorageSchemeCfg configuration,
      List<String>             unacceptableReasons
      )
  {
    // A delete should always be acceptable, so just return true.
    return true;
@@ -932,33 +326,139 @@
  /**
   * Attempts to apply a new configuration based on the provided deleted entry.
   *
   * @param  configEntry  The new configuration entry that has been deleted.
   *
   * @return  Information about the result of processing the configuration
   *          change.
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry)
  public ConfigChangeResult applyConfigurationDelete(
      PasswordStorageSchemeCfg configuration
      )
  {
    DN         configEntryDN       = configEntry.getDN();
    ResultCode resultCode          = ResultCode.SUCCESS;
    boolean    adminActionRequired = false;
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    uninstallPasswordStorageScheme (configuration.dn());
    return changeResult;
  }
    // See if the entry is registered as a password storage scheme.  If so,
    // deregister it and stop the storage scheme.
    PasswordStorageScheme storageScheme = storageSchemes.remove(configEntryDN);
    if (storageScheme != null)
  /**
   * Loads the specified class, instantiates it as a password storage scheme,
   * and optionally initializes that instance. Any initialized password
   * storage scheme is registered in the server.
   *
   * @param  className      The fully-qualified name of the password storage
   *                        scheme class to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the
   *                        password storage scheme, or {@code null} if the
   *                        password storage scheme should not be initialized.
   *
   * @throws  InitializationException  If a problem occurred while attempting
   *                                   to initialize the class.
   */
  private void loadAndInstallPasswordStorageScheme(
       String className,
       PasswordStorageSchemeCfg configuration
       )
       throws InitializationException
  {
    // Load the password storage scheme class...
    PasswordStorageScheme
        <? extends PasswordStorageSchemeCfg> schemeClass;
    schemeClass = loadPasswordStorageScheme (className, configuration);
    // ... and install the password storage scheme in the server.
    DN configEntryDN = configuration.dn();
    storageSchemes.put (configEntryDN, schemeClass);
    DirectoryServer.registerPasswordStorageScheme (schemeClass);
  }
  /**
   * Loads the specified class, instantiates it as a password storage scheme,
   * and optionally initializes that instance.
   *
   * @param  className      The fully-qualified name of the class
   *                        to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the
   *                        class, or {@code null} if the
   *                        class should not be initialized.
   *
   * @return  The possibly initialized password storage scheme.
   *
   * @throws  InitializationException  If a problem occurred while attempting
   *                                   to initialize the class.
   */
  private PasswordStorageScheme <? extends PasswordStorageSchemeCfg>
    loadPasswordStorageScheme(
       String className,
       PasswordStorageSchemeCfg configuration
       )
       throws InitializationException
  {
    try
    {
      String lowerName = toLowerCase(storageScheme.getStorageSchemeName());
      DirectoryServer.deregisterPasswordStorageScheme(lowerName);
      PasswordStorageSchemeCfgDefn definition;
      ClassPropertyDefinition propertyDefinition;
      Class<? extends PasswordStorageScheme> schemeClass;
      PasswordStorageScheme<? extends PasswordStorageSchemeCfg>
          passwordStorageScheme;
      storageScheme.finalizePasswordStorageScheme();
      definition = PasswordStorageSchemeCfgDefn.getInstance();
      propertyDefinition = definition.getSchemeClassPropertyDefinition();
      schemeClass = propertyDefinition.loadClass(
          className,
          PasswordStorageScheme.class
          );
      passwordStorageScheme =
        (PasswordStorageScheme<? extends PasswordStorageSchemeCfg>)
            schemeClass.newInstance();
      if (configuration != null)
      {
        Method method = passwordStorageScheme.getClass().getMethod(
            "initializePasswordStorageScheme",
            configuration.definition().getServerConfigurationClass()
            );
        method.invoke(passwordStorageScheme, configuration);
      }
      return passwordStorageScheme;
    }
    catch (Exception e)
    {
      int msgID = MSGID_CONFIG_PWSCHEME_INITIALIZATION_FAILED;
      String message = getMessage(
          msgID, className,
          String.valueOf(configuration.dn()),
          stackTraceToSingleLineString(e)
          );
      throw new InitializationException(msgID, message, e);
    }
  }
    return new ConfigChangeResult(resultCode, adminActionRequired);
  /**
   * Remove a password storage that has been installed in the server.
   *
   * @param configEntryDN  the DN of the configuration enry associated to
   *                       the password storage scheme to remove
   */
  private void uninstallPasswordStorageScheme(
      DN configEntryDN
      )
  {
    PasswordStorageScheme scheme =
        storageSchemes.remove (configEntryDN);
    if (scheme != null)
    {
      DirectoryServer.deregisterPasswordStorageScheme (
          scheme.getStorageSchemeName().toLowerCase()
          );
      scheme.finalizePasswordStorageScheme();
    }
  }
}
opends/src/server/org/opends/server/extensions/Base64PasswordStorageScheme.java
@@ -28,8 +28,8 @@
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringFactory;
@@ -53,7 +53,7 @@
 * value from the casual observer.
 */
public class Base64PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
@@ -75,8 +75,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    // No initialization is required.
  }
opends/src/server/org/opends/server/extensions/ClearPasswordStorageScheme.java
@@ -28,8 +28,8 @@
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringFactory;
@@ -52,7 +52,7 @@
 * applications.
 */
public class ClearPasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
@@ -74,8 +74,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
  throws ConfigException, InitializationException
  {
    // No initialization is required.
  }
opends/src/server/org/opends/server/extensions/DefaultEntryCache.java
@@ -28,17 +28,21 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.EntryCacheCfg;
import org.opends.server.api.Backend;
import org.opends.server.api.EntryCache;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LockType;
import org.opends.server.types.ResultCode;
@@ -50,7 +54,8 @@
 * <CODE>putEntry</CODE> will return immediately without doing anything.
 */
public class DefaultEntryCache
       extends EntryCache
       extends EntryCache<EntryCacheCfg>
       implements ConfigurationChangeListener<EntryCacheCfg>
{
@@ -67,21 +72,9 @@
  /**
   * Initializes this entry cache implementation so that it will be available
   * for storing and retrieving entries.
   *
   * @param  configEntry  The configuration entry containing the settings to use
   *                      for this entry cache.
   *
   * @throws  ConfigException  If there is a problem with the provided
   *                           configuration entry that would prevent this
   *                           entry cache from being used.
   *
   * @throws  InitializationException  If a problem occurs during the
   *                                   initialization process that is not
   *                                   related to the configuration.
   * {@inheritDoc}
   */
  public void initializeEntryCache(ConfigEntry configEntry)
  public void initializeEntryCache(EntryCacheCfg configEntry)
         throws ConfigException, InitializationException
  {
    // No implementation required.
@@ -90,9 +83,7 @@
  /**
   * Performs any necessary cleanup work (e.g., flushing all cached entries and
   * releasing any other held resources) that should be performed when the
   * server is to be shut down or the entry cache destroyed or replaced.
   * {@inheritDoc}
   */
  public void finalizeEntryCache()
  {
@@ -102,14 +93,7 @@
  /**
   * Indicates whether the entry cache currently contains the entry with the
   * specified DN.  This method may be called without holding any locks if a
   * point-in-time check is all that is required.
   *
   * @param  entryDN  The DN for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the entry cache currently contains the entry
   *          with the specified DN, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean containsEntry(DN entryDN)
  {
@@ -120,14 +104,7 @@
  /**
   * Retrieves the entry with the specified DN from the cache.  The caller
   * should have already acquired a read or write lock for the entry if such
   * protection is needed.
   *
   * @param  entryDN  The DN of the entry to retrieve.
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(DN entryDN)
  {
@@ -138,14 +115,7 @@
  /**
   * Retrieves the entry ID for the entry with the specified DN from the cache.
   * The caller should have already acquired a read or write lock for the entry
   * if such protection is needed.
   *
   * @param  entryDN  The DN of the entry for which to retrieve the entry ID.
   *
   * @return  The entry ID for the requested entry, or -1 if it is not present
   *          in the cache.
   * {@inheritDoc}
   */
  public long getEntryID(DN entryDN)
  {
@@ -156,20 +126,7 @@
  /**
   * Retrieves the entry with the specified DN from the cache, obtaining a lock
   * on the entry before it is returned.  If the entry is present in the cache,
   * then a lock will be obtained for that entry and appended to the provided
   * list before the entry is returned.  If the entry is not present, then no
   * lock will be obtained.
   *
   * @param  entryDN   The DN of the entry to retrieve.
   * @param  lockType  The type of lock to obtain (it may be <CODE>NONE</CODE>).
   * @param  lockList  The list to which the obtained lock will be added (note
   *                   that no lock will be added if the lock type was
   *                   <CODE>NONE</CODE>).
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(DN entryDN, LockType lockType, List<Lock> lockList)
  {
@@ -180,22 +137,7 @@
  /**
   * Retrieves the requested entry if it is present in the cache, obtaining a
   * lock on the entry before it is returned.  If the entry is present in the
   * cache, then a lock  will be obtained for that entry and appended to the
   * provided list before the entry is returned.  If the entry is not present,
   * then no lock will be obtained.
   *
   * @param  backend   The backend associated with the entry to retrieve.
   * @param  entryID   The entry ID within the provided backend for the
   *                   specified entry.
   * @param  lockType  The type of lock to obtain (it may be <CODE>NONE</CODE>).
   * @param  lockList  The list to which the obtained lock will be added (note
   *                   that no lock will be added if the lock type was
   *                   <CODE>NONE</CODE>).
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(Backend backend, long entryID, LockType lockType,
                        List<Lock> lockList)
@@ -207,14 +149,7 @@
  /**
   * Stores the provided entry in the cache.  Note that the mechanism that it
   * uses to achieve this is implementation-dependent, and it is acceptable for
   * the entry to not actually be stored in any cache.
   *
   * @param  entry    The entry to store in the cache.
   * @param  backend  The backend with which the entry is associated.
   * @param  entryID  The entry ID within the provided backend that uniquely
   *                  identifies the specified entry.
   * {@inheritDoc}
   */
  public void putEntry(Entry entry, Backend backend, long entryID)
  {
@@ -224,22 +159,7 @@
  /**
   * Stores the provided entry in the cache only if it does not conflict with an
   * entry that already exists.  Note that the mechanism that it uses to achieve
   * this is implementation-dependent, and it is acceptable for the entry to not
   * actually be stored in any cache.  However, this method must not overwrite
   * an existing version of the entry.
   *
   * @param  entry    The entry to store in the cache.
   * @param  backend  The backend with which the entry is associated.
   * @param  entryID  The entry ID within the provided backend that uniquely
   *                  identifies the specified entry.
   *
   * @return  <CODE>false</CODE> if an existing entry or some other problem
   *          prevented the method from completing successfully, or
   *          <CODE>true</CODE> if there was no conflict and the entry was
   *          either stored or the cache determined that this entry should never
   *          be cached for some reason.
   * {@inheritDoc}
   */
  public boolean putEntryIfAbsent(Entry entry, Backend backend, long entryID)
  {
@@ -251,9 +171,7 @@
  /**
   * Removes the specified entry from the cache.
   *
   * @param  entryDN  The DN of the entry to remove from the cache.
   * {@inheritDoc}
   */
  public void removeEntry(DN entryDN)
  {
@@ -263,8 +181,7 @@
  /**
   * Removes all entries from the cache.  The cache should still be available
   * for future use.
   * {@inheritDoc}
   */
  public void clear()
  {
@@ -274,10 +191,7 @@
  /**
   * Removes all entries from the cache that are associated with the provided
   * backend.
   *
   * @param  backend  The backend for which to flush the associated entries.
   * {@inheritDoc}
   */
  public void clearBackend(Backend backend)
  {
@@ -287,9 +201,7 @@
  /**
   * Removes all entries from the cache that are below the provided DN.
   *
   * @param  baseDN  The base DN below which all entries should be flushed.
   * {@inheritDoc}
   */
  public void clearSubtree(DN baseDN)
  {
@@ -299,15 +211,44 @@
  /**
   * Attempts to react to a scenario in which it is determined that the system
   * is running low on available memory.  In this case, the entry cache should
   * attempt to free some memory if possible to try to avoid out of memory
   * errors.
   * {@inheritDoc}
   */
  public void handleLowMemory()
  {
    // This implementation does not store entries, so there are no resources
    // that it can free.
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      EntryCacheCfg configuration,
      List<String>  unacceptableReasons
      )
  {
    // No implementation required.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      EntryCacheCfg configuration
      )
  {
    // No implementation required.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<String>()
        );
    return changeResult;
    }
}
opends/src/server/org/opends/server/extensions/EntryCacheCommon.java
New file
@@ -0,0 +1,333 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import static org.opends.server.loggers.Error.logError;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.SortedSet;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
/**
 * This class provides some common tools to all entry cache implementations.
 */
public class EntryCacheCommon
{
  /**
   * Configuration phases. Each value identifies a configuration step:
   * - PHASE_INIT       when invoking method initializeEntryCache()
   * - PHASE_ACCEPTABLE when invoking method isConfigurationChangeAcceptable()
   * - PHASE_APPLY      when invoking method applyConfigurationChange()
   */
  public static enum ConfigPhase
  {
    /**
     * Indicates that entry cache is in initialization check phase.
     */
    PHASE_INIT,
    /**
     * Indicates that entry cache is in configuration check phase.
     */
    PHASE_ACCEPTABLE,
    /**
     * Indicates that entry cache is applying its configuration.
     */
    PHASE_APPLY
  }
  /**
   * Error handler used by local methods to report configuration error.
   * The error handler simplifies the code of initializeEntryCache(),
   * isConfigurationChangeAcceptable() and applyConfigurationChanges() methods.
   */
  public class ConfigErrorHandler
  {
    // Configuration phase.
    private EntryCacheCommon.ConfigPhase _configPhase;
    // Unacceptable reasons. Used when _configPhase is PHASE_ACCEPTABLE.
    private List<String> _unacceptableReasons;
    // Error messages. Used when _configPhase is PHASE_APPLY.
    private ArrayList<String> _errorMessages;
    // Result code. Used when _configPhase is PHASE_APPLY.
    private ResultCode _resultCode;
    // Acceptable Configuration ? Used when _configPhase is PHASE_ACCEPTABLE
    // or PHASE_APPLY.
    private boolean _isAcceptable;
    /**
     * Create an error handler.
     *
     * @param configPhase          the configuration phase for which the
     *                             error handler is used
     * @param unacceptableReasons  the reasons why the configuration cannot
     *                             be applied (during PHASE_ACCEPTABLE phase)
     * @param errorMessages        the errors found when applying a new
     *                             configuration (during PHASE_APPLY phase)
     */
    public ConfigErrorHandler (
        EntryCacheCommon.ConfigPhase configPhase,
        List<String>                 unacceptableReasons,
        ArrayList<String>            errorMessages
        )
    {
      _configPhase         = configPhase;
      _unacceptableReasons = unacceptableReasons;
      _errorMessages       = errorMessages;
      _resultCode          = ResultCode.SUCCESS;
      _isAcceptable        = true;
    }
    /**
     * Report an error.
     *
     * @param category     the category of the error to report
     * @param severity     the severity of the error to report
     * @param errorID      the error ID of the error to report
     * @param arg1         the first  argument of the error message
     * @param arg2         the second argument of the error message
     * @param arg3         the third  argument of the error message
     * @param isAcceptable <code>true</code> if the configuration is acceptable
     * @param resultCode   the change result for the current configuration
     */
    public void reportError(
        ErrorLogCategory category,
        ErrorLogSeverity severity,
        int              errorID,
        String           arg1,
        String           arg2,
        String           arg3,
        boolean          isAcceptable,
        ResultCode       resultCode
        )
    {
      switch (_configPhase)
      {
      case PHASE_INIT:
        {
        logError (category, severity, errorID, arg1, arg2, arg3);
        break;
        }
      case PHASE_ACCEPTABLE:
        {
        String message = getMessage (errorID, arg1, arg2, arg3);
        _unacceptableReasons.add (message);
        _isAcceptable = isAcceptable;
        break;
        }
      case PHASE_APPLY:
        {
        String message = getMessage (errorID, arg1, arg2, arg3);
        _errorMessages.add (message);
        _isAcceptable = isAcceptable;
        if (_resultCode == ResultCode.SUCCESS)
        {
          _resultCode = resultCode;
        }
        break;
        }
      }
    }
    /**
     * Get the current result code that was elaborated right after a
     * configuration has been applied.
     *
     * @return the current result code
     */
    public ResultCode getResultCode()
    {
      return _resultCode;
    }
    /**
     * Get the current isAcceptable flag. The isAcceptable flag is elaborated
     * right after the configuration was checked.
     *
     * @return the isAcceptable flag
     */
    public boolean getIsAcceptable()
    {
      return _isAcceptable;
    }
    /**
     * Get the current unacceptable reasons. The unacceptable reasons are
     * elaborated when the configuration is checked.
     *
     * @return the list of unacceptable reasons
     */
    public List<String> getUnacceptableReasons()
    {
      return _unacceptableReasons;
    }
    /**
     * Get the current error messages. The error messages are elaborated
     * when the configuration is applied.
     *
     * @return the list of error messages
     */
    public ArrayList<String> getErrorMessages()
    {
      return _errorMessages;
    }
    /**
     * Get the current configuration phase. The configuration phase indicates
     * whether the entry cache is in initialization step, or in configuration
     * checking step or in configuration being applied step.
     *
     * @return the current configuration phase.
     */
    public ConfigPhase getConfigPhase()
    {
      return _configPhase;
    }
  } // ConfigErrorHandler
  /**
   * Reads a list of string filters and convert it to a list of search
   * filters.
   *
   * @param filters  the list of string filter to convert to search filters
   * @param decodeErrorMsgId  the error message ID to use in case of error
   * @param errorHandler      an handler used to report errors
   *                          during decoding of filter
   * @param noFilterMsgId     the error message ID to use when none of the
   *                          filters was decoded properly
   * @param configEntryDN     the DN of the configuration entry for the
   *                          entry cache
   *
   * @return the set of search filters
   */
  public static HashSet<SearchFilter> getFilters (
      SortedSet<String>  filters,
      int                decodeErrorMsgId,
      int                noFilterMsgId,
      ConfigErrorHandler errorHandler,
      DN                 configEntryDN
      )
  {
    // Returned value
    HashSet<SearchFilter> searchFilters = new HashSet<SearchFilter>();
    // Convert the string filters to search filters.
    if (filters != null)
    {
      for (String curFilter: filters)
      {
        try
        {
          searchFilters.add (SearchFilter.createFilterFromString (curFilter));
        }
        catch (DirectoryException de)
        {
          // We couldn't decode this filter. Log a warning and continue.
          errorHandler.reportError(
              ErrorLogCategory.CONFIGURATION,
              ErrorLogSeverity.SEVERE_WARNING,
              decodeErrorMsgId,
              String.valueOf(configEntryDN),
              curFilter,
              stackTraceToSingleLineString (de),
              false,
              ResultCode.INVALID_ATTRIBUTE_SYNTAX
              );
        }
      }
      // If none of the filters was decoded properly log an error message
      // (only if we are in initialize phase).
      if ((errorHandler.getConfigPhase() == ConfigPhase.PHASE_INIT)
          && searchFilters.isEmpty())
      {
        errorHandler.reportError(
            ErrorLogCategory.CONFIGURATION,
            ErrorLogSeverity.SEVERE_ERROR,
            noFilterMsgId,
            null,
            null,
            null,
            false,
            null
            );
      }
    }
    // done
    return searchFilters;
  }
  /**
   * Create a new error handler.
   *
   * @param configPhase          the configuration phase for which the
   *                             error handler is used
   * @param unacceptableReasons  the reasons why the configuration cannot
   *                             be applied (during PHASE_ACCEPTABLE phase)
   * @param errorMessages        the errors found when applying a new
   *                             configuration (during PHASE_APPLY phase)
   *
   * @return a new configuration error handler
   */
  public static ConfigErrorHandler getConfigErrorHandler (
      EntryCacheCommon.ConfigPhase  configPhase,
      List<String>                  unacceptableReasons,
      ArrayList<String>             errorMessages
      )
  {
    ConfigErrorHandler errorHandler = null;
    EntryCacheCommon ec = new EntryCacheCommon();
    errorHandler = ec.new ConfigErrorHandler (
        configPhase, unacceptableReasons, errorMessages
        );
    return errorHandler;
  }
}
opends/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandler.java
@@ -28,18 +28,25 @@
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.Error.logError;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.getMessage;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.
       ErrorLogAccountStatusNotificationHandlerCfgDefn;
import org.opends.server.admin.std.server.
       ErrorLogAccountStatusNotificationHandlerCfg;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.MultiChoiceConfigAttribute;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.AccountStatusNotificationType;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
@@ -48,15 +55,6 @@
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
@@ -65,12 +63,13 @@
 * logging facility.
 */
public class ErrorLogAccountStatusNotificationHandler
       extends AccountStatusNotificationHandler
       implements ConfigurableComponent
       extends
          AccountStatusNotificationHandler
          <ErrorLogAccountStatusNotificationHandlerCfg>
       implements
          ConfigurationChangeListener
          <ErrorLogAccountStatusNotificationHandlerCfg>
{
  /**
   * The set of names for the account status notification types that may be
   * logged by this notification handler.
@@ -78,17 +77,6 @@
  private static final HashSet<String> NOTIFICATION_TYPE_NAMES =
       new HashSet<String>();
  // The DN of the configuration entry for this notification handler.
  private DN configEntryDN;
  // The set of notification types that should generate log messages.
  private HashSet<AccountStatusNotificationType> notificationTypes;
  static
  {
    for (AccountStatusNotificationType t :
@@ -99,88 +87,34 @@
  }
  // The DN of the configuration entry for this notification handler.
  private DN configEntryDN;
  // The set of notification types that should generate log messages.
  private HashSet<AccountStatusNotificationType> notificationTypes;
  /**
   * Initializes this account status notification handler based on the
   * information in the provided configuration entry.
   *
   * @param  configEntry  The configuration entry that contains the information
   *                      to use to initialize this account status notification
   *                      handler.
   *
   * @throws  ConfigException  If the provided entry does not contain a valid
   *                           configuration for this account status
   *                           notification handler.
   *
   * @throws  InitializationException  If a problem occurs during initialization
   *                                   that is not related to the server
   *                                   configuration.
   * {@inheritDoc}
   */
  public void initializeStatusNotificationHandler(ConfigEntry configEntry)
       throws ConfigException, InitializationException
  public void initializeStatusNotificationHandler(
      ErrorLogAccountStatusNotificationHandlerCfg configuration
      )
      throws ConfigException, InitializationException
  {
    configEntryDN = configEntry.getDN();
    configuration.addErrorLogChangeListener (this);
    configEntryDN = configuration.dn();
    // Initialize the set of notification types that should generate log
    // messages.
    int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES;
    MultiChoiceConfigAttribute typesStub =
         new MultiChoiceConfigAttribute(ATTR_ACCT_NOTIFICATION_TYPE,
                                        getMessage(msgID), true, true, false,
                                        NOTIFICATION_TYPE_NAMES);
    try
    {
      MultiChoiceConfigAttribute typesAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(typesStub);
      notificationTypes = new HashSet<AccountStatusNotificationType>();
      for (String s : typesAttr.activeValues())
      {
        AccountStatusNotificationType t =
             AccountStatusNotificationType.typeForName(s);
        if (t == null)
        {
          msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE;
          String message = getMessage(msgID, String.valueOf(configEntryDN), s);
          throw new ConfigException(msgID, message);
        }
        else
        {
          notificationTypes.add(t);
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  getExceptionMessage(e));
      throw new InitializationException(msgID, message, e);
    }
    DirectoryServer.registerConfigurableComponent(this);
    DirectoryServer.registerAccountStatusNotificationHandler(configEntryDN,
                                                             this);
    // Read configuration and apply changes.
    boolean applyChanges = true;
    processNotificationHandlerConfig (configuration, applyChanges);
  }
  /**
   * Performs any processing that may be necessary in conjunction with the
   * provided account status notification type.
   *
   * @param  notificationType  The type for this account status notification.
   * @param  userDN            The DN of the user entry to which this
   *                           notification applies.
   * @param  messageID         The unique ID for this notification.
   * @param  message           The human-readable message for this notification.
   * {@inheritDoc}
   */
  public void handleStatusNotification(AccountStatusNotificationType
                                            notificationType,
@@ -240,158 +174,172 @@
  /**
   * Indicates whether the provided configuration entry has an
   * acceptable configuration for this component.  If it does not,
   * then detailed information about the problem(s) should be added to
   * the provided list.
   *
   * @param  configEntry          The configuration entry for which to
   *                              make the determination.
   * @param  unacceptableReasons  A list that can be used to hold
   *                              messages about why the provided
   *                              entry does not have an acceptable
   *                              configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an
   *          acceptable configuration for this component, or
   *          <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                      List<String> unacceptableReasons)
  public boolean isConfigurationChangeAcceptable(
      ErrorLogAccountStatusNotificationHandlerCfg configuration,
      List<String> unacceptableReasons
      )
  {
    // Initialize the set of notification types that should generate log
    // messages.
    int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES;
    MultiChoiceConfigAttribute typesStub =
         new MultiChoiceConfigAttribute(ATTR_ACCT_NOTIFICATION_TYPE,
                                        getMessage(msgID), true, true, false,
                                        NOTIFICATION_TYPE_NAMES);
    try
    {
      MultiChoiceConfigAttribute typesAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(typesStub);
      HashSet<AccountStatusNotificationType> types =
           new HashSet<AccountStatusNotificationType>();
      for (String s : typesAttr.activeValues())
      {
        AccountStatusNotificationType t =
             AccountStatusNotificationType.typeForName(s);
        if (t == null)
        {
          msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE;
          String message = getMessage(msgID, String.valueOf(configEntryDN), s);
          unacceptableReasons.add(message);
          return false;
        }
        else
        {
          types.add(t);
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
    // Make sure that we can process the defined notification handler.
    // If so, then we'll accept the new configuration.
    boolean applyChanges = false;
    boolean isAcceptable = processNotificationHandlerConfig (
        configuration, applyChanges
        );
      msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  getExceptionMessage(e));
      unacceptableReasons.add(message);
      return false;
    }
    // If we've gotten here, then everything is OK.
    return true;
    return isAcceptable;
  }
  /**
   * Makes a best-effort attempt to apply the configuration contained
   * in the provided entry.  Information about the result of this
   * processing should be added to the provided message list.
   * Information should always be added to this list if a
   * configuration change could not be applied.  If detailed results
   * are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not
   * changed) should also be included.
   * Makes a best-effort attempt to apply the configuration contained in the
   * provided entry.  Information about the result of this processing should be
   * added to the provided message list.  Information should always be added to
   * this list if a configuration change could not be applied.  If detailed
   * results are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not changed) should
   * also be included.
   *
   * @param  configEntry      The entry containing the new
   *                          configuration to apply for this
   *                          component.
   * @param  detailedResults  Indicates whether detailed information
   *                          about the processing should be added to
   *                          the list.
   * @param  configuration    The entry containing the new configuration to
   *                          apply for this component.
   * @param  detailedResults  Indicates whether detailed information about the
   *                          processing should be added to the list.
   *
   * @return  Information about the result of the configuration
   *          update.
   * @return  Information about the result of the configuration update.
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
  public ConfigChangeResult applyConfigurationChange (
      ErrorLogAccountStatusNotificationHandlerCfg configuration,
      boolean detailedResults
      )
  {
    ConfigChangeResult changeResult = applyConfigurationChange (configuration);
    return changeResult;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange (
      ErrorLogAccountStatusNotificationHandlerCfg configuration
      )
  {
    ResultCode resultCode = ResultCode.SUCCESS;
    boolean adminActionRequired = false;
    ArrayList<String> messages = new ArrayList<String>();
    ConfigChangeResult changeResult = new ConfigChangeResult(
        resultCode, adminActionRequired, messages
        );
    // Initialize the set of notification types that should generate log
    // messages.
    HashSet<AccountStatusNotificationType> types =
         new HashSet<AccountStatusNotificationType>();
    int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES;
    MultiChoiceConfigAttribute typesStub =
         new MultiChoiceConfigAttribute(ATTR_ACCT_NOTIFICATION_TYPE,
                                        getMessage(msgID), true, true, false,
                                        NOTIFICATION_TYPE_NAMES);
    try
    {
      MultiChoiceConfigAttribute typesAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(typesStub);
      for (String s : typesAttr.activeValues())
      {
        AccountStatusNotificationType t =
             AccountStatusNotificationType.typeForName(s);
        if (t == null)
        {
          resultCode = ResultCode.UNWILLING_TO_PERFORM;
    boolean applyChanges = false;
    processNotificationHandlerConfig (
        configuration, applyChanges
        );
          msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE;
          messages.add(getMessage(msgID, String.valueOf(configEntryDN), s));
        }
        else
        {
          types.add(t);
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      resultCode = DirectoryServer.getServerErrorResultCode();
      msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      this.notificationTypes = types;
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return changeResult;
  }
  /**
   * Parses the provided configuration and configure the notification handler.
   *
   * @param configuration  The new configuration containing the changes.
   * @param applyChanges   If true then take into account the new configuration.
   *
   * @return  The mapping between strings of character set values and the
   *          minimum number of characters required from those sets.
   */
  public boolean processNotificationHandlerConfig(
      ErrorLogAccountStatusNotificationHandlerCfg configuration,
      boolean                                     applyChanges
      )
  {
    // false if the configuration is not acceptable
    boolean isAcceptable = true;
    // The set of notification types that should generate log messages.
    HashSet<AccountStatusNotificationType> newNotificationTypes =
        new HashSet<AccountStatusNotificationType>();
    // Initialize the set of notification types that should generate log
    // messages.
    for (ErrorLogAccountStatusNotificationHandlerCfgDefn.
         AccountStatusNotificationType configNotificationType:
         configuration.getAccountStatusNotificationType())
    {
      newNotificationTypes.add (getNotificationType (configNotificationType));
    }
    if (applyChanges && isAcceptable)
    {
      notificationTypes = newNotificationTypes;
    }
    return isAcceptable;
  }
  /**
   * Gets the OpenDS notification type object that corresponds to the
   * configuration counterpart.
   *
   * @param  notificationType  The configuration notification type for which
   *                           to retrieve the OpenDS notification type.
   */
  private AccountStatusNotificationType getNotificationType(
      ErrorLogAccountStatusNotificationHandlerCfgDefn.
         AccountStatusNotificationType configNotificationType
      )
  {
    AccountStatusNotificationType nt = null;
    switch (configNotificationType)
    {
    case ACCOUNT_TEMPORARILY_LOCKED:
         nt = AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED;
         break;
    case ACCOUNT_PERMANENTLY_LOCKED:
         nt = AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED;
         break;
    case ACCOUNT_UNLOCKED:
         nt = AccountStatusNotificationType.ACCOUNT_UNLOCKED;
         break;
    case ACCOUNT_IDLE_LOCKED:
         nt = AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED;
         break;
    case ACCOUNT_RESET_LOCKED:
         nt = AccountStatusNotificationType.ACCOUNT_RESET_LOCKED;
         break;
    case ACCOUNT_DISABLED:
         nt = AccountStatusNotificationType.ACCOUNT_DISABLED;
         break;
    case ACCOUNT_ENABLED:
         nt = AccountStatusNotificationType.ACCOUNT_ENABLED;
         break;
    case ACCOUNT_EXPIRED:
         nt = AccountStatusNotificationType.ACCOUNT_EXPIRED;
         break;
    case PASSWORD_EXPIRED:
         nt = AccountStatusNotificationType.PASSWORD_EXPIRED;
         break;
    case PASSWORD_EXPIRING:
         nt = AccountStatusNotificationType.PASSWORD_EXPIRING;
         break;
    case PASSWORD_RESET:
         nt = AccountStatusNotificationType.PASSWORD_RESET;
         break;
    case PASSWORD_CHANGED:
         nt = AccountStatusNotificationType.PASSWORD_CHANGED;
         break;
    }
    return nt;
  }
}
opends/src/server/org/opends/server/extensions/FIFOEntryCache.java
@@ -41,10 +41,10 @@
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.api.Backend;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.api.EntryCache;
import org.opends.server.admin.std.server.FIFOEntryCacheCfg;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.IntegerConfigAttribute;
import org.opends.server.config.IntegerWithUnitConfigAttribute;
@@ -54,8 +54,6 @@
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LockManager;
import org.opends.server.types.LockType;
@@ -66,11 +64,9 @@
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -100,8 +96,8 @@
 * of the given filters will be stored in the cache.
 */
public class FIFOEntryCache
       extends EntryCache
       implements ConfigurableComponent
       extends EntryCache <FIFOEntryCacheCfg>
       implements ConfigurationChangeListener<FIFOEntryCacheCfg>
{
@@ -179,291 +175,35 @@
  /**
   * Initializes this entry cache implementation so that it will be available
   * for storing and retrieving entries.
   *
   * @param  configEntry  The configuration entry containing the settings to use
   *                      for this entry cache.
   *
   * @throws  ConfigException  If there is a problem with the provided
   *                           configuration entry that would prevent this
   *                           entry cache from being used.
   *
   * @throws  InitializationException  If a problem occurs during the
   *                                   initialization process that is not
   *                                   related to the configuration.
   * {@inheritDoc}
   */
  public void initializeEntryCache(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializeEntryCache(
      FIFOEntryCacheCfg configuration
      )
      throws ConfigException, InitializationException
  {
    configEntryDN = configEntry.getDN();
    configuration.addFIFOChangeListener (this);
    configEntryDN = configuration.dn();
    // Initialize the cache structures.
    idMap     = new HashMap<Backend,HashMap<Long,CacheEntry>>();
    dnMap     = new LinkedHashMap<DN,CacheEntry>();
    cacheLock = new ReentrantLock();
    runtime   = Runtime.getRuntime();
    // Determine the maximum memory usage as a percentage of the total JVM
    // memory.
    maxMemoryPercent = DEFAULT_FIFOCACHE_MAX_MEMORY_PCT;
    int msgID = MSGID_FIFOCACHE_DESCRIPTION_MAX_MEMORY_PCT;
    IntegerConfigAttribute maxMemoryPctStub =
         new IntegerConfigAttribute(ATTR_FIFOCACHE_MAX_MEMORY_PCT,
                                    getMessage(msgID), true, false, false, true,
                                    1, true, 100);
    try
    {
      IntegerConfigAttribute maxMemoryPctAttr =
           (IntegerConfigAttribute)
           configEntry.getConfigAttribute(maxMemoryPctStub);
      if (maxMemoryPctAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        maxMemoryPercent = maxMemoryPctAttr.activeIntValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_FIFOCACHE_CANNOT_DETERMINE_MAX_MEMORY_PCT,
               String.valueOf(configEntryDN), getExceptionMessage(e),
               maxMemoryPercent);
    }
    maxAllowedMemory = runtime.maxMemory() / 100 * maxMemoryPercent;
    // Determine the maximum number of entries that we will allow in the cache.
    maxEntries = DEFAULT_FIFOCACHE_MAX_ENTRIES;
    msgID = MSGID_FIFOCACHE_DESCRIPTION_MAX_ENTRIES;
    IntegerConfigAttribute maxEntriesStub =
         new IntegerConfigAttribute(ATTR_FIFOCACHE_MAX_ENTRIES,
                                    getMessage(msgID), true, false, false,
                                    true, 0, false, 0);
    try
    {
      IntegerConfigAttribute maxEntriesAttr =
           (IntegerConfigAttribute)
           configEntry.getConfigAttribute(maxEntriesStub);
      if (maxEntriesAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        maxEntries = maxEntriesAttr.activeValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_FIFOCACHE_CANNOT_DETERMINE_MAX_ENTRIES,
               String.valueOf(configEntryDN), getExceptionMessage(e));
    }
    // Determine the lock timeout to use when interacting with the lock manager.
    lockTimeout = DEFAULT_FIFOCACHE_LOCK_TIMEOUT;
    msgID = MSGID_FIFOCACHE_DESCRIPTION_LOCK_TIMEOUT;
    IntegerWithUnitConfigAttribute lockTimeoutStub =
         new IntegerWithUnitConfigAttribute(ATTR_FIFOCACHE_LOCK_TIMEOUT,
                                            getMessage(msgID), false, timeUnits,
                                            true, 0, false, 0);
    try
    {
      IntegerWithUnitConfigAttribute lockTimeoutAttr =
             (IntegerWithUnitConfigAttribute)
             configEntry.getConfigAttribute(lockTimeoutStub);
      if (lockTimeoutAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        lockTimeout = lockTimeoutAttr.activeCalculatedValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_FIFOCACHE_CANNOT_DETERMINE_LOCK_TIMEOUT,
               String.valueOf(configEntryDN), getExceptionMessage(e),
               lockTimeout);
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be included in the cache.
    includeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_FIFOCACHE_DESCRIPTION_INCLUDE_FILTERS;
    StringConfigAttribute includeStub =
         new StringConfigAttribute(ATTR_FIFOCACHE_INCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute includeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(includeStub);
      if (includeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = includeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no include filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              includeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter.  Log a warning and continue.
              logError(ErrorLogCategory.CONFIGURATION,
                       ErrorLogSeverity.SEVERE_WARNING,
                       MSGID_FIFOCACHE_CANNOT_DECODE_INCLUDE_FILTER,
                       String.valueOf(configEntryDN), filterString,
                       getExceptionMessage(e));
            }
          }
          if (includeFilters.isEmpty())
          {
            logError(ErrorLogCategory.CONFIGURATION,
                     ErrorLogSeverity.SEVERE_ERROR,
                     MSGID_FIFOCACHE_CANNOT_DECODE_ANY_INCLUDE_FILTERS,
                     String.valueOf(configEntryDN));
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_FIFOCACHE_CANNOT_DETERMINE_INCLUDE_FILTERS,
               String.valueOf(configEntryDN), getExceptionMessage(e));
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be excluded from the cache.
    excludeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_FIFOCACHE_DESCRIPTION_EXCLUDE_FILTERS;
    StringConfigAttribute excludeStub =
         new StringConfigAttribute(ATTR_FIFOCACHE_EXCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute excludeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(excludeStub);
      if (excludeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = excludeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no exclude filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              excludeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter.  Log a warning and continue.
              logError(ErrorLogCategory.CONFIGURATION,
                       ErrorLogSeverity.SEVERE_WARNING,
                       MSGID_FIFOCACHE_CANNOT_DECODE_EXCLUDE_FILTER,
                       String.valueOf(configEntryDN), filterString,
                       getExceptionMessage(e));
            }
          }
          if (excludeFilters.isEmpty())
          {
            logError(ErrorLogCategory.CONFIGURATION,
                     ErrorLogSeverity.SEVERE_ERROR,
                     MSGID_FIFOCACHE_CANNOT_DECODE_ANY_EXCLUDE_FILTERS,
                     String.valueOf(configEntryDN));
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_FIFOCACHE_CANNOT_DETERMINE_EXCLUDE_FILTERS,
               String.valueOf(configEntryDN), getExceptionMessage(e));
    }
    // Read configuration and apply changes.
    boolean applyChanges = true;
    EntryCacheCommon.ConfigErrorHandler errorHandler =
      EntryCacheCommon.getConfigErrorHandler (
          EntryCacheCommon.ConfigPhase.PHASE_INIT, null, null
          );
    processEntryCacheConfig (configuration, applyChanges, errorHandler);
  }
  /**
   * Performs any necessary cleanup work (e.g., flushing all cached entries and
   * releasing any other held resources) that should be performed when the
   * server is to be shut down or the entry cache destroyed or replaced.
   * {@inheritDoc}
   */
  public void finalizeEntryCache()
  {
@@ -492,14 +232,7 @@
  /**
   * Indicates whether the entry cache currently contains the entry with the
   * specified DN.  This method may be called without holding any locks if a
   * point-in-time check is all that is required.
   *
   * @param  entryDN  The DN for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the entry cache currently contains the entry
   *          with the specified DN, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean containsEntry(DN entryDN)
  {
@@ -510,14 +243,7 @@
  /**
   * Retrieves the entry with the specified DN from the cache.  The caller
   * should have already acquired a read or write lock for the entry if such
   * protection is needed.
   *
   * @param  entryDN  The DN of the entry to retrieve.
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(DN entryDN)
  {
@@ -536,14 +262,7 @@
  /**
   * Retrieves the entry ID for the entry with the specified DN from the cache.
   * The caller should have already acquired a read or write lock for the entry
   * if such protection is needed.
   *
   * @param  entryDN  The DN of the entry for which to retrieve the entry ID.
   *
   * @return  The entry ID for the requested entry, or -1 if it is not present
   *          in the cache.
   * {@inheritDoc}
   */
  public long getEntryID(DN entryDN)
  {
@@ -562,20 +281,7 @@
  /**
   * Retrieves the entry with the specified DN from the cache, obtaining a lock
   * on the entry before it is returned.  If the entry is present in the cache,
   * then a lock will be obtained for that entry and appended to the provided
   * list before the entry is returned.  If the entry is not present, then no
   * lock will be obtained.
   *
   * @param  entryDN   The DN of the entry to retrieve.
   * @param  lockType  The type of lock to obtain (it may be <CODE>NONE</CODE>).
   * @param  lockList  The list to which the obtained lock will be added (note
   *                   that no lock will be added if the lock type was
   *                   <CODE>NONE</CODE>).
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(DN entryDN, LockType lockType, List<Lock> lockList)
  {
@@ -684,22 +390,7 @@
  /**
   * Retrieves the requested entry if it is present in the cache, obtaining a
   * lock on the entry before it is returned.  If the entry is present in the
   * cache, then a lock  will be obtained for that entry and appended to the
   * provided list before the entry is returned.  If the entry is not present,
   * then no lock will be obtained.
   *
   * @param  backend   The backend associated with the entry to retrieve.
   * @param  entryID   The entry ID within the provided backend for the
   *                   specified entry.
   * @param  lockType  The type of lock to obtain (it may be <CODE>NONE</CODE>).
   * @param  lockList  The list to which the obtained lock will be added (note
   *                   that no lock will be added if the lock type was
   *                   <CODE>NONE</CODE>).
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(Backend backend, long entryID, LockType lockType,
                        List<Lock> lockList)
@@ -820,14 +511,7 @@
  /**
   * Stores the provided entry in the cache.  Note that the mechanism that it
   * uses to achieve this is implementation-dependent, and it is acceptable for
   * the entry to not actually be stored in any cache.
   *
   * @param  entry    The entry to store in the cache.
   * @param  backend  The backend with which the entry is associated.
   * @param  entryID  The entry ID within the provided backend that uniquely
   *                  identifies the specified entry.
   * {@inheritDoc}
   */
  public void putEntry(Entry entry, Backend backend, long entryID)
  {
@@ -999,22 +683,7 @@
  /**
   * Stores the provided entry in the cache only if it does not conflict with an
   * entry that already exists.  Note that the mechanism that it uses to achieve
   * this is implementation-dependent, and it is acceptable for the entry to not
   * actually be stored in any cache.  However, this method must not overwrite
   * an existing version of the entry.
   *
   * @param  entry    The entry to store in the cache.
   * @param  backend  The backend with which the entry is associated.
   * @param  entryID  The entry ID within the provided backend that uniquely
   *                  identifies the specified entry.
   *
   * @return  <CODE>false</CODE> if an existing entry or some other problem
   *          prevented the method from completing successfully, or
   *          <CODE>true</CODE> if there was no conflict and the entry was
   *          either stored or the cache determined that this entry should never
   *          be cached for some reason.
   * {@inheritDoc}
   */
  public boolean putEntryIfAbsent(Entry entry, Backend backend, long entryID)
  {
@@ -1201,9 +870,7 @@
  /**
   * Removes the specified entry from the cache.
   *
   * @param  entryDN  The DN of the entry to remove from the cache.
   * {@inheritDoc}
   */
  public void removeEntry(DN entryDN)
  {
@@ -1257,8 +924,7 @@
  /**
   * Removes all entries from the cache.  The cache should still be available
   * for future use.
   * {@inheritDoc}
   */
  public void clear()
  {
@@ -1295,10 +961,7 @@
  /**
   * Removes all entries from the cache that are associated with the provided
   * backend.
   *
   * @param  backend  The backend for which to flush the associated entries.
   * {@inheritDoc}
   */
  public void clearBackend(Backend backend)
  {
@@ -1359,9 +1022,7 @@
  /**
   * Removes all entries from the cache that are below the provided DN.
   *
   * @param  baseDN  The base DN below which all entries should be flushed.
   * {@inheritDoc}
   */
  public void clearSubtree(DN baseDN)
  {
@@ -1403,11 +1064,7 @@
  /**
   * Clears all entries at or below the specified base DN that are associated
   * with the given backend.  The caller must already hold the cache lock.
   *
   * @param  baseDN   The base DN below which all entries should be flushed.
   * @param  backend  The backend for which to remove the appropriate entries.
   * {@inheritDoc}
   */
  private void clearSubtree(DN baseDN, Backend backend)
  {
@@ -1474,10 +1131,7 @@
  /**
   * Attempts to react to a scenario in which it is determined that the system
   * is running low on available memory.  In this case, the entry cache should
   * attempt to free some memory if possible to try to avoid out of memory
   * errors.
   * {@inheritDoc}
   */
  public void handleLowMemory()
  {
@@ -1618,235 +1272,55 @@
  /**
   * Indicates whether the provided configuration entry has an acceptable
   * configuration for this component.  If it does not, then detailed
   * information about the problem(s) should be added to the provided list.
   *
   * @param  configEntry          The configuration entry for which to make the
   *                              determination.
   * @param  unacceptableReasons  A list that can be used to hold messages about
   *                              why the provided entry does not have an
   *                              acceptable configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an acceptable
   *          configuration for this component, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                                            List<String> unacceptableReasons)
  public boolean isConfigurationChangeAcceptable(
      FIFOEntryCacheCfg configuration,
      List<String>      unacceptableReasons
      )
  {
    // Start out assuming that the configuration is valid.
    boolean configIsAcceptable = true;
    // Make sure that we can process the defined character sets.  If so, then
    // we'll accept the new configuration.
    boolean applyChanges = false;
    EntryCacheCommon.ConfigErrorHandler errorHandler =
      EntryCacheCommon.getConfigErrorHandler (
          EntryCacheCommon.ConfigPhase.PHASE_ACCEPTABLE,
          unacceptableReasons,
          null
        );
    processEntryCacheConfig (configuration, applyChanges, errorHandler);
    return errorHandler.getIsAcceptable();
  }
    // Determine the maximum memory usage as a percentage of the total JVM
    // memory.
    int msgID = MSGID_FIFOCACHE_DESCRIPTION_MAX_MEMORY_PCT;
    IntegerConfigAttribute maxMemoryPctStub =
         new IntegerConfigAttribute(ATTR_FIFOCACHE_MAX_MEMORY_PCT,
                                    getMessage(msgID), true, false, false, true,
                                    1, true, 100);
    try
    {
      IntegerConfigAttribute maxMemoryPctAttr =
           (IntegerConfigAttribute)
           configEntry.getConfigAttribute(maxMemoryPctStub);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_MAX_MEMORY_PCT;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      FIFOEntryCacheCfg configuration
      )
  {
    // Make sure that we can process the defined character sets.  If so, then
    // activate the new configuration.
    boolean applyChanges = false;
    ArrayList<String> errorMessages = new ArrayList<String>();
    EntryCacheCommon.ConfigErrorHandler errorHandler =
      EntryCacheCommon.getConfigErrorHandler (
          EntryCacheCommon.ConfigPhase.PHASE_APPLY, null, errorMessages
          );
    processEntryCacheConfig (configuration, applyChanges, errorHandler);
    // Determine the maximum number of entries that we will allow in the cache.
    msgID = MSGID_FIFOCACHE_DESCRIPTION_MAX_ENTRIES;
    IntegerConfigAttribute maxEntriesStub =
         new IntegerConfigAttribute(ATTR_FIFOCACHE_MAX_ENTRIES,
                                    getMessage(msgID), true, false, false,
                                    true, 0, false, 0);
    try
    {
      IntegerConfigAttribute maxEntriesAttr =
           (IntegerConfigAttribute)
           configEntry.getConfigAttribute(maxEntriesStub);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
    boolean adminActionRequired = false;
    ConfigChangeResult changeResult = new ConfigChangeResult(
        errorHandler.getResultCode(),
        adminActionRequired,
        errorHandler.getErrorMessages()
        );
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_MAX_ENTRIES;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
    // Determine the lock timeout to use when interacting with the lock manager.
    msgID = MSGID_FIFOCACHE_DESCRIPTION_LOCK_TIMEOUT;
    IntegerWithUnitConfigAttribute lockTimeoutStub =
         new IntegerWithUnitConfigAttribute(ATTR_FIFOCACHE_LOCK_TIMEOUT,
                                            getMessage(msgID), false, timeUnits,
                                            true, 0, false, 0);
    try
    {
      IntegerWithUnitConfigAttribute lockTimeoutAttr =
             (IntegerWithUnitConfigAttribute)
             configEntry.getConfigAttribute(lockTimeoutStub);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_LOCK_TIMEOUT;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be included in the cache.
    msgID = MSGID_FIFOCACHE_DESCRIPTION_INCLUDE_FILTERS;
    StringConfigAttribute includeStub =
         new StringConfigAttribute(ATTR_FIFOCACHE_INCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute includeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(includeStub);
      if (includeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = includeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no include filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              SearchFilter.createFilterFromString(filterString);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_FIFOCACHE_INVALID_INCLUDE_FILTER;
              unacceptableReasons.add(getMessage(msgID,
                                           String.valueOf(configEntryDN),
                                            filterString,
                                            getExceptionMessage(e)));
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_INCLUDE_FILTERS;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be excluded from the cache.
    msgID = MSGID_FIFOCACHE_DESCRIPTION_EXCLUDE_FILTERS;
    StringConfigAttribute excludeStub =
         new StringConfigAttribute(ATTR_FIFOCACHE_EXCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute excludeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(excludeStub);
      if (excludeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = excludeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no exclude filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              SearchFilter.createFilterFromString(filterString);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_FIFOCACHE_INVALID_EXCLUDE_FILTER;
              unacceptableReasons.add(getMessage(msgID,
                                           String.valueOf(configEntryDN),
                                            filterString,
                                            getExceptionMessage(e)));
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_EXCLUDE_FILTERS;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
    return configIsAcceptable;
    return changeResult;
  }
@@ -1860,335 +1334,159 @@
   * successfully (and optionally about parameters that were not changed) should
   * also be included.
   *
   * @param  configEntry      The entry containing the new configuration to
   * @param  configuration    The entry containing the new configuration to
   *                          apply for this component.
   * @param  detailedResults  Indicates whether detailed information about the
   *                          processing should be added to the list.
   *
   * @return  Information about the result of the configuration update.
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
  public ConfigChangeResult applyNewConfiguration(
      FIFOEntryCacheCfg configuration,
      boolean           detailedResults
      )
  {
    // Create a set of variables to use for the result.
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    boolean           configIsAcceptable  = true;
    // Store the current value to detect changes.
    long                  prevLockTimeout      = lockTimeout;
    long                  prevMaxEntries       = maxEntries;
    int                   prevMaxMemoryPercent = maxMemoryPercent;
    HashSet<SearchFilter> prevIncludeFilters   = includeFilters;
    HashSet<SearchFilter> prevExcludeFilters   = excludeFilters;
    // Activate the new configuration.
    ConfigChangeResult changeResult = applyConfigurationChange(configuration);
    // Determine the maximum memory usage as a percentage of the total JVM
    // memory.
    int newMaxMemoryPercent = DEFAULT_FIFOCACHE_MAX_MEMORY_PCT;
    int msgID = MSGID_FIFOCACHE_DESCRIPTION_MAX_MEMORY_PCT;
    IntegerConfigAttribute maxMemoryPctStub =
         new IntegerConfigAttribute(ATTR_FIFOCACHE_MAX_MEMORY_PCT,
                                    getMessage(msgID), true, false, false, true,
                                    1, true, 100);
    try
    // Add detailed messages if needed.
    ResultCode resultCode = changeResult.getResultCode();
    boolean configIsAcceptable = (resultCode == ResultCode.SUCCESS);
    if (detailedResults && configIsAcceptable)
    {
      IntegerConfigAttribute maxMemoryPctAttr =
           (IntegerConfigAttribute)
           configEntry.getConfigAttribute(maxMemoryPctStub);
      if (maxMemoryPctAttr != null)
      if (maxMemoryPercent != prevMaxMemoryPercent)
      {
        newMaxMemoryPercent = maxMemoryPctAttr.pendingIntValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
        changeResult.addMessage(
            getMessage(
                MSGID_FIFOCACHE_UPDATED_MAX_MEMORY_PCT,
                maxMemoryPercent,
                maxAllowedMemory));
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_MAX_MEMORY_PCT;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      resultCode = ResultCode.CONSTRAINT_VIOLATION;
      configIsAcceptable = false;
    }
    // Determine the maximum number of entries that we will allow in the cache.
    long newMaxEntries = DEFAULT_FIFOCACHE_MAX_ENTRIES;
    msgID = MSGID_FIFOCACHE_DESCRIPTION_MAX_ENTRIES;
    IntegerConfigAttribute maxEntriesStub =
         new IntegerConfigAttribute(ATTR_FIFOCACHE_MAX_ENTRIES,
                                    getMessage(msgID), true, false, false,
                                    true, 0, false, 0);
    try
    {
      IntegerConfigAttribute maxEntriesAttr =
           (IntegerConfigAttribute)
           configEntry.getConfigAttribute(maxEntriesStub);
      if (maxEntriesAttr != null)
      if (maxEntries != prevMaxEntries)
      {
        newMaxEntries = maxEntriesAttr.pendingValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
        changeResult.addMessage(
            getMessage (MSGID_FIFOCACHE_UPDATED_MAX_ENTRIES, maxEntries));
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_MAX_ENTRIES;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      if (resultCode == ResultCode.SUCCESS)
      if (lockTimeout != prevLockTimeout)
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
        changeResult.addMessage(
            getMessage (MSGID_FIFOCACHE_UPDATED_LOCK_TIMEOUT, lockTimeout));
      }
      configIsAcceptable = false;
    }
    // Determine the lock timeout to use when interacting with the lock manager.
    long newLockTimeout = DEFAULT_FIFOCACHE_LOCK_TIMEOUT;
    msgID = MSGID_FIFOCACHE_DESCRIPTION_LOCK_TIMEOUT;
    IntegerWithUnitConfigAttribute lockTimeoutStub =
         new IntegerWithUnitConfigAttribute(ATTR_FIFOCACHE_LOCK_TIMEOUT,
                                            getMessage(msgID), false, timeUnits,
                                            true, 0, false, 0);
    try
    {
      IntegerWithUnitConfigAttribute lockTimeoutAttr =
             (IntegerWithUnitConfigAttribute)
             configEntry.getConfigAttribute(lockTimeoutStub);
      if (lockTimeoutAttr != null)
      if (!includeFilters.equals(prevIncludeFilters))
      {
        newLockTimeout = lockTimeoutAttr.pendingCalculatedValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
        changeResult.addMessage(
            getMessage (MSGID_FIFOCACHE_UPDATED_INCLUDE_FILTERS));
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_LOCK_TIMEOUT;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      if (resultCode == ResultCode.SUCCESS)
      if (!excludeFilters.equals(prevExcludeFilters))
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
      }
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be included in the cache.
    HashSet<SearchFilter> newIncludeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_FIFOCACHE_DESCRIPTION_INCLUDE_FILTERS;
    StringConfigAttribute includeStub =
         new StringConfigAttribute(ATTR_FIFOCACHE_INCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute includeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(includeStub);
      if (includeAttr != null)
      {
        List<String> filterStrings = includeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no include filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              newIncludeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_FIFOCACHE_INVALID_INCLUDE_FILTER;
              messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                      filterString,
                                      getExceptionMessage(e)));
              if (resultCode == ResultCode.SUCCESS)
              {
                resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
              }
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_INCLUDE_FILTERS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
      }
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be exclude from the cache.
    HashSet<SearchFilter> newExcludeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_FIFOCACHE_DESCRIPTION_EXCLUDE_FILTERS;
    StringConfigAttribute excludeStub =
         new StringConfigAttribute(ATTR_FIFOCACHE_EXCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute excludeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(excludeStub);
      if (excludeAttr != null)
      {
        List<String> filterStrings = excludeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no exclude filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              newExcludeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_FIFOCACHE_INVALID_EXCLUDE_FILTER;
              messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                      filterString, getExceptionMessage(e)));
              if (resultCode == ResultCode.SUCCESS)
              {
                resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
              }
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_FIFOCACHE_INVALID_EXCLUDE_FILTERS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
      }
      configIsAcceptable = false;
    }
    if (configIsAcceptable)
    {
      if (maxMemoryPercent != newMaxMemoryPercent)
      {
        maxMemoryPercent = newMaxMemoryPercent;
        maxAllowedMemory = runtime.maxMemory() / 100 * maxMemoryPercent;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_FIFOCACHE_UPDATED_MAX_MEMORY_PCT,
                                  maxMemoryPercent, maxAllowedMemory));
        }
      }
      if (maxEntries != newMaxEntries)
      {
        maxEntries = newMaxEntries;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_FIFOCACHE_UPDATED_MAX_ENTRIES,
                                  maxEntries));
        }
      }
      if (lockTimeout != newLockTimeout)
      {
        lockTimeout = newLockTimeout;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_FIFOCACHE_UPDATED_LOCK_TIMEOUT,
                                  lockTimeout));
        }
      }
      if (!includeFilters.equals(newIncludeFilters))
      {
        includeFilters = newIncludeFilters;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_FIFOCACHE_UPDATED_INCLUDE_FILTERS));
        }
      }
      if (!excludeFilters.equals(newExcludeFilters))
      {
        excludeFilters = newExcludeFilters;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_FIFOCACHE_UPDATED_EXCLUDE_FILTERS));
        }
        changeResult.addMessage(
            getMessage (MSGID_FIFOCACHE_UPDATED_EXCLUDE_FILTERS));
      }
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    return changeResult;
  }
  /**
   * Parses the provided configuration and configure the entry cache.
   *
   * @param configuration  The new configuration containing the changes.
   * @param applyChanges   If true then take into account the new configuration.
   * @param errorHandler   An handler used to report errors.
   *
   * @return  The mapping between strings of character set values and the
   *          minimum number of characters required from those sets.
   */
  public boolean processEntryCacheConfig(
      FIFOEntryCacheCfg                   configuration,
      boolean                             applyChanges,
      EntryCacheCommon.ConfigErrorHandler errorHandler
      )
  {
    // Local variables to read configuration.
    DN                    newConfigEntryDN;
    long                  newLockTimeout;
    long                  newMaxEntries;
    int                   newMaxMemoryPercent;
    long                  newMaxAllowedMemory;
    HashSet<SearchFilter> newIncludeFilters = null;
    HashSet<SearchFilter> newExcludeFilters = null;
    // Read configuration.
    newConfigEntryDN = configuration.dn();
    newLockTimeout   = configuration.getLockTimeout();
    newMaxEntries    = configuration.getMaxEntries();
    // Maximum memory the cache can use.
    newMaxMemoryPercent = (int) configuration.getMaxMemoryPercent();
    long maxJvmHeapSize = Runtime.getRuntime().maxMemory();
    newMaxAllowedMemory = (maxJvmHeapSize / 100) * newMaxMemoryPercent;
    // Get include and exclude filters.
    switch (errorHandler.getConfigPhase())
    {
    case PHASE_INIT:
      newIncludeFilters = EntryCacheCommon.getFilters (
          configuration.getIncludeFilter(),
          MSGID_FIFOCACHE_INVALID_INCLUDE_FILTER,
          MSGID_FIFOCACHE_CANNOT_DECODE_ANY_INCLUDE_FILTERS,
          errorHandler,
          configEntryDN
          );
      newExcludeFilters = EntryCacheCommon.getFilters (
          configuration.getExcludeFilter(),
          MSGID_FIFOCACHE_CANNOT_DECODE_EXCLUDE_FILTER,
          MSGID_FIFOCACHE_CANNOT_DECODE_ANY_EXCLUDE_FILTERS,
          errorHandler,
          configEntryDN
          );
      break;
    case PHASE_ACCEPTABLE:  // acceptable and apply are using the same
    case PHASE_APPLY:       // error ID codes
      newIncludeFilters = EntryCacheCommon.getFilters (
          configuration.getIncludeFilter(),
          MSGID_FIFOCACHE_INVALID_INCLUDE_FILTER,
          0,
          errorHandler,
          configEntryDN
          );
      newExcludeFilters = EntryCacheCommon.getFilters (
          configuration.getExcludeFilter(),
          MSGID_FIFOCACHE_INVALID_EXCLUDE_FILTER,
          0,
          errorHandler,
          configEntryDN
          );
      break;
    }
    if (applyChanges && errorHandler.getIsAcceptable())
    {
      configEntryDN    = newConfigEntryDN;
      lockTimeout      = newLockTimeout;
      maxEntries       = newMaxEntries;
      maxMemoryPercent = newMaxMemoryPercent;
      maxAllowedMemory = newMaxAllowedMemory;
      includeFilters   = newIncludeFilters;
      excludeFilters   = newExcludeFilters;
    }
    return errorHandler.getIsAcceptable();
  }
}
opends/src/server/org/opends/server/extensions/MD5PasswordStorageScheme.java
@@ -32,8 +32,8 @@
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ByteString;
@@ -66,7 +66,7 @@
 * vulnerable to dictionary attacks than salted variants.
 */
public class MD5PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
  /**
   * The fully-qualified name of this class.
@@ -101,8 +101,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    try
    {
opends/src/server/org/opends/server/extensions/SHA1PasswordStorageScheme.java
@@ -32,8 +32,8 @@
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ByteString;
@@ -66,7 +66,7 @@
 * vulnerable to dictionary attacks than salted variants.
 */
public class SHA1PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
  /**
   * The fully-qualified name of this class.
@@ -101,8 +101,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    try
    {
opends/src/server/org/opends/server/extensions/SaltedMD5PasswordStorageScheme.java
@@ -33,8 +33,8 @@
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ByteString;
@@ -69,7 +69,7 @@
 * appended to the hash, and then the entire value is base64-encoded.
 */
public class SaltedMD5PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
  /**
   * The fully-qualified name of this class.
@@ -115,8 +115,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    try
    {
opends/src/server/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java
@@ -33,8 +33,8 @@
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ByteString;
@@ -69,7 +69,7 @@
 * appended to the hash, and then the entire value is base64-encoded.
 */
public class SaltedSHA1PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
  /**
   * The fully-qualified name of this class.
@@ -115,8 +115,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    try
    {
opends/src/server/org/opends/server/extensions/SaltedSHA256PasswordStorageScheme.java
@@ -33,8 +33,8 @@
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ByteString;
@@ -69,7 +69,7 @@
 * is appended to the hash, and then the entire value is base64-encoded.
 */
public class SaltedSHA256PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
  /**
   * The fully-qualified name of this class.
@@ -116,8 +116,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    try
    {
opends/src/server/org/opends/server/extensions/SaltedSHA384PasswordStorageScheme.java
@@ -33,8 +33,8 @@
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ByteString;
@@ -69,7 +69,7 @@
 * is appended to the hash, and then the entire value is base64-encoded.
 */
public class SaltedSHA384PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
  /**
   * The fully-qualified name of this class.
@@ -116,8 +116,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    try
    {
opends/src/server/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java
@@ -33,8 +33,8 @@
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ByteString;
@@ -69,7 +69,7 @@
 * is appended to the hash, and then the entire value is base64-encoded.
 */
public class SaltedSHA512PasswordStorageScheme
       extends PasswordStorageScheme
       extends PasswordStorageScheme <PasswordStorageSchemeCfg>
{
  /**
   * The fully-qualified name of this class.
@@ -116,8 +116,10 @@
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordStorageScheme(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializePasswordStorageScheme(
      PasswordStorageSchemeCfg configuration
      )
      throws ConfigException, InitializationException
  {
    try
    {
opends/src/server/org/opends/server/extensions/SoftReferenceEntryCache.java
@@ -39,10 +39,10 @@
import java.util.concurrent.locks.Lock;
import org.opends.server.api.Backend;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.api.EntryCache;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.SoftReferenceEntryCacheCfg;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.IntegerWithUnitConfigAttribute;
import org.opends.server.config.StringConfigAttribute;
@@ -51,23 +51,19 @@
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LockManager;
import org.opends.server.types.LockType;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -77,21 +73,18 @@
 * running low on memory.
 */
public class SoftReferenceEntryCache
       extends EntryCache
       implements ConfigurableComponent, Runnable
    extends EntryCache <SoftReferenceEntryCacheCfg>
    implements
        ConfigurationChangeListener<SoftReferenceEntryCacheCfg>,
        Runnable
{
  /**
   * The set of time units that will be used for expressing the task retention
   * time.
   */
  // The set of time units that will be used for expressing the task retention
  // time.
  private static final LinkedHashMap<String,Double> timeUnits =
       new LinkedHashMap<String,Double>();
  // The mapping between entry DNs and their corresponding entries.
  private ConcurrentHashMap<DN,SoftReference<CacheEntry>> dnMap;
@@ -158,213 +151,32 @@
  /**
   * Initializes this entry cache implementation so that it will be available
   * for storing and retrieving entries.
   *
   * @param  configEntry  The configuration entry containing the settings to use
   *                      for this entry cache.
   *
   * @throws  ConfigException  If there is a problem with the provided
   *                           configuration entry that would prevent this
   *                           entry cache from being used.
   *
   * @throws  InitializationException  If a problem occurs during the
   *                                   initialization process that is not
   *                                   related to the configuration.
   * {@inheritDoc}
   */
  public void initializeEntryCache(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  public void initializeEntryCache(
      SoftReferenceEntryCacheCfg configuration
      )
      throws ConfigException, InitializationException
  {
    configuration.addSoftReferenceChangeListener (this);
    configEntryDN = configuration.dn();
    dnMap.clear();
    idMap.clear();
    configEntryDN = configEntry.getDN();
    // Determine the lock timeout to use when interacting with the lock manager.
    int msgID = MSGID_SOFTREFCACHE_DESCRIPTION_LOCK_TIMEOUT;
    IntegerWithUnitConfigAttribute lockTimeoutStub =
         new IntegerWithUnitConfigAttribute(ATTR_SOFTREFCACHE_LOCK_TIMEOUT,
                                            getMessage(msgID), false, timeUnits,
                                            true, 0, false, 0);
    try
    {
      IntegerWithUnitConfigAttribute lockTimeoutAttr =
             (IntegerWithUnitConfigAttribute)
             configEntry.getConfigAttribute(lockTimeoutStub);
      if (lockTimeoutAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        lockTimeout = lockTimeoutAttr.activeCalculatedValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_SOFTREFCACHE_CANNOT_DETERMINE_LOCK_TIMEOUT,
               String.valueOf(configEntryDN), getExceptionMessage(e),
               lockTimeout);
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be included in the cache.
    includeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_SOFTREFCACHE_DESCRIPTION_INCLUDE_FILTERS;
    StringConfigAttribute includeStub =
         new StringConfigAttribute(ATTR_SOFTREFCACHE_INCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute includeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(includeStub);
      if (includeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = includeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no include filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              includeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter.  Log a warning and continue.
              logError(ErrorLogCategory.CONFIGURATION,
                       ErrorLogSeverity.SEVERE_WARNING,
                       MSGID_SOFTREFCACHE_CANNOT_DECODE_INCLUDE_FILTER,
                       String.valueOf(configEntryDN), filterString,
                       getExceptionMessage(e));
            }
          }
          if (includeFilters.isEmpty())
          {
            logError(ErrorLogCategory.CONFIGURATION,
                     ErrorLogSeverity.SEVERE_ERROR,
                     MSGID_SOFTREFCACHE_CANNOT_DECODE_ANY_INCLUDE_FILTERS,
                     String.valueOf(configEntryDN));
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_SOFTREFCACHE_CANNOT_DETERMINE_INCLUDE_FILTERS,
               String.valueOf(configEntryDN), getExceptionMessage(e));
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be excluded from the cache.
    excludeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_SOFTREFCACHE_DESCRIPTION_EXCLUDE_FILTERS;
    StringConfigAttribute excludeStub =
         new StringConfigAttribute(ATTR_SOFTREFCACHE_EXCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute excludeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(excludeStub);
      if (excludeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = excludeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no exclude filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              excludeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter.  Log a warning and continue.
              logError(ErrorLogCategory.CONFIGURATION,
                       ErrorLogSeverity.SEVERE_WARNING,
                       MSGID_SOFTREFCACHE_CANNOT_DECODE_EXCLUDE_FILTER,
                       String.valueOf(configEntryDN), filterString,
                       getExceptionMessage(e));
            }
          }
          if (excludeFilters.isEmpty())
          {
            logError(ErrorLogCategory.CONFIGURATION,
                     ErrorLogSeverity.SEVERE_ERROR,
                     MSGID_SOFTREFCACHE_CANNOT_DECODE_ANY_EXCLUDE_FILTERS,
                     String.valueOf(configEntryDN));
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // Log an error message.
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               MSGID_SOFTREFCACHE_CANNOT_DETERMINE_EXCLUDE_FILTERS,
               String.valueOf(configEntryDN), getExceptionMessage(e));
    }
    // Read configuration and apply changes.
    boolean applyChanges = true;
    EntryCacheCommon.ConfigErrorHandler errorHandler =
      EntryCacheCommon.getConfigErrorHandler (
          EntryCacheCommon.ConfigPhase.PHASE_INIT, null, null
          );
    processEntryCacheConfig (configuration, applyChanges, errorHandler);
  }
  /**
   * Performs any necessary cleanup work (e.g., flushing all cached entries and
   * releasing any other held resources) that should be performed when the
   * server is to be shut down or the entry cache destroyed or replaced.
   * {@inheritDoc}
   */
  public void finalizeEntryCache()
  {
@@ -375,14 +187,7 @@
  /**
   * Indicates whether the entry cache currently contains the entry with the
   * specified DN.  This method may be called without holding any locks if a
   * point-in-time check is all that is required.
   *
   * @param  entryDN  The DN for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the entry cache currently contains the entry
   *          with the specified DN, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean containsEntry(DN entryDN)
  {
@@ -393,14 +198,7 @@
  /**
   * Retrieves the entry with the specified DN from the cache.  The caller
   * should have already acquired a read or write lock for the entry if such
   * protection is needed.
   *
   * @param  entryDN  The DN of the entry to retrieve.
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(DN entryDN)
  {
@@ -426,14 +224,7 @@
  /**
   * Retrieves the entry ID for the entry with the specified DN from the cache.
   * The caller should have already acquired a read or write lock for the entry
   * if such protection is needed.
   *
   * @param  entryDN  The DN of the entry for which to retrieve the entry ID.
   *
   * @return  The entry ID for the requested entry, or -1 if it is not present
   *          in the cache.
   * {@inheritDoc}
   */
  public long getEntryID(DN entryDN)
  {
@@ -459,20 +250,7 @@
  /**
   * Retrieves the entry with the specified DN from the cache, obtaining a lock
   * on the entry before it is returned.  If the entry is present in the cache,
   * then a lock will be obtained for that entry and appended to the provided
   * list before the entry is returned.  If the entry is not present, then no
   * lock will be obtained.
   *
   * @param  entryDN   The DN of the entry to retrieve.
   * @param  lockType  The type of lock to obtain (it may be <CODE>NONE</CODE>).
   * @param  lockList  The list to which the obtained lock will be added (note
   *                   that no lock will be added if the lock type was
   *                   <CODE>NONE</CODE>).
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(DN entryDN, LockType lockType,
                        List<Lock> lockList)
@@ -590,22 +368,7 @@
  /**
   * Retrieves the requested entry if it is present in the cache, obtaining a
   * lock on the entry before it is returned.  If the entry is present in the
   * cache, then a lock  will be obtained for that entry and appended to the
   * provided list before the entry is returned.  If the entry is not present,
   * then no lock will be obtained.
   *
   * @param  backend   The backend associated with the entry to retrieve.
   * @param  entryID   The entry ID within the provided backend for the
   *                   specified entry.
   * @param  lockType  The type of lock to obtain (it may be <CODE>NONE</CODE>).
   * @param  lockList  The list to which the obtained lock will be added (note
   *                   that no lock will be added if the lock type was
   *                   <CODE>NONE</CODE>).
   *
   * @return  The requested entry if it is present in the cache, or
   *          <CODE>null</CODE> if it is not present.
   * {@inheritDoc}
   */
  public Entry getEntry(Backend backend, long entryID,
                        LockType lockType, List<Lock> lockList)
@@ -725,14 +488,7 @@
  /**
   * Stores the provided entry in the cache.  Note that the mechanism that it
   * uses to achieve this is implementation-dependent, and it is acceptable for
   * the entry to not actually be stored in any cache.
   *
   * @param  entry    The entry to store in the cache.
   * @param  backend  The backend with which the entry is associated.
   * @param  entryID  The entry ID within the provided backend that uniquely
   *                  identifies the specified entry.
   * {@inheritDoc}
   */
  public void putEntry(Entry entry, Backend backend, long entryID)
  {
@@ -827,22 +583,7 @@
  /**
   * Stores the provided entry in the cache only if it does not conflict with an
   * entry that already exists.  Note that the mechanism that it uses to achieve
   * this is implementation-dependent, and it is acceptable for the entry to not
   * actually be stored in any cache.  However, this method must not overwrite
   * an existing version of the entry.
   *
   * @param  entry    The entry to store in the cache.
   * @param  backend  The backend with which the entry is associated.
   * @param  entryID  The entry ID within the provided backend that uniquely
   *                  identifies the specified entry.
   *
   * @return  <CODE>false</CODE> if an existing entry or some other problem
   *          prevented the method from completing successfully, or
   *          <CODE>true</CODE> if there was no conflict and the entry was
   *          either stored or the cache determined that this entry should never
   *          be cached for some reason.
   * {@inheritDoc}
   */
  public boolean putEntryIfAbsent(Entry entry, Backend backend,
                                  long entryID)
@@ -940,9 +681,7 @@
  /**
   * Removes the specified entry from the cache.
   *
   * @param  entryDN  The DN of the entry to remove from the cache.
   * {@inheritDoc}
   */
  public void removeEntry(DN entryDN)
  {
@@ -973,8 +712,7 @@
  /**
   * Removes all entries from the cache.  The cache should still be available
   * for future use.
   * {@inheritDoc}
   */
  public void clear()
  {
@@ -985,10 +723,7 @@
  /**
   * Removes all entries from the cache that are associated with the provided
   * backend.
   *
   * @param  backend  The backend for which to flush the associated entries.
   * {@inheritDoc}
   */
  public void clearBackend(Backend backend)
  {
@@ -1015,9 +750,7 @@
  /**
   * Removes all entries from the cache that are below the provided DN.
   *
   * @param  baseDN  The base DN below which all entries should be flushed.
   * {@inheritDoc}
   */
  public void clearSubtree(DN baseDN)
  {
@@ -1037,10 +770,7 @@
  /**
   * Attempts to react to a scenario in which it is determined that the system
   * is running low on available memory.  In this case, the entry cache should
   * attempt to free some memory if possible to try to avoid out of memory
   * errors.
   * {@inheritDoc}
   */
  public void handleLowMemory()
  {
@@ -1120,431 +850,135 @@
  /**
   * Indicates whether the provided configuration entry has an acceptable
   * configuration for this component.  If it does not, then detailed
   * information about the problem(s) should be added to the provided list.
   *
   * @param  configEntry          The configuration entry for which to make the
   *                              determination.
   * @param  unacceptableReasons  A list that can be used to hold messages about
   *                              why the provided entry does not have an
   *                              acceptable configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an acceptable
   *          configuration for this component, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                                            List<String> unacceptableReasons)
  public boolean isConfigurationChangeAcceptable(
      SoftReferenceEntryCacheCfg configuration,
      List<String>               unacceptableReasons)
  {
    // Start out assuming that the configuration is valid.
    boolean configIsAcceptable = true;
    // Make sure that we can process the defined character sets.  If so, then
    // we'll accept the new configuration.
    boolean applyChanges = false;
    EntryCacheCommon.ConfigErrorHandler errorHandler =
      EntryCacheCommon.getConfigErrorHandler (
          EntryCacheCommon.ConfigPhase.PHASE_ACCEPTABLE,
          unacceptableReasons,
          null
        );
    processEntryCacheConfig (configuration, applyChanges, errorHandler);
    // Determine the lock timeout to use when interacting with the lock manager.
    int msgID = MSGID_SOFTREFCACHE_DESCRIPTION_LOCK_TIMEOUT;
    IntegerWithUnitConfigAttribute lockTimeoutStub =
         new IntegerWithUnitConfigAttribute(ATTR_SOFTREFCACHE_LOCK_TIMEOUT,
                                            getMessage(msgID), false, timeUnits,
                                            true, 0, false, 0);
    try
    {
      IntegerWithUnitConfigAttribute lockTimeoutAttr =
             (IntegerWithUnitConfigAttribute)
             configEntry.getConfigAttribute(lockTimeoutStub);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_SOFTREFCACHE_INVALID_LOCK_TIMEOUT;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be included in the cache.
    msgID = MSGID_SOFTREFCACHE_DESCRIPTION_INCLUDE_FILTERS;
    StringConfigAttribute includeStub =
         new StringConfigAttribute(ATTR_SOFTREFCACHE_INCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute includeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(includeStub);
      if (includeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = includeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no include filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              SearchFilter.createFilterFromString(filterString);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_SOFTREFCACHE_INVALID_INCLUDE_FILTER;
              unacceptableReasons.add(getMessage(msgID,
                                           String.valueOf(configEntryDN),
                                            filterString,
                                            getExceptionMessage(e)));
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_SOFTREFCACHE_INVALID_INCLUDE_FILTERS;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be excluded from the cache.
    msgID = MSGID_SOFTREFCACHE_DESCRIPTION_EXCLUDE_FILTERS;
    StringConfigAttribute excludeStub =
         new StringConfigAttribute(ATTR_SOFTREFCACHE_EXCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute excludeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(excludeStub);
      if (excludeAttr == null)
      {
        // This is fine -- we'll just use the default.
      }
      else
      {
        List<String> filterStrings = excludeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no exclude filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              SearchFilter.createFilterFromString(filterString);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_SOFTREFCACHE_INVALID_EXCLUDE_FILTER;
              unacceptableReasons.add(getMessage(msgID,
                                           String.valueOf(configEntryDN),
                                           filterString,
                                           getExceptionMessage(e)));
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_SOFTREFCACHE_INVALID_EXCLUDE_FILTERS;
      unacceptableReasons.add(getMessage(msgID, String.valueOf(configEntryDN),
                                         getExceptionMessage(e)));
      configIsAcceptable = false;
    }
    return configIsAcceptable;
    return errorHandler.getIsAcceptable();
  }
  /**
   * Makes a best-effort attempt to apply the configuration contained in the
   * provided entry.  Information about the result of this processing should be
   * added to the provided message list.  Information should always be added to
   * this list if a configuration change could not be applied.  If detailed
   * results are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not changed) should
   * also be included.
   *
   * @param  configEntry      The entry containing the new configuration to
   *                          apply for this component.
   * @param  detailedResults  Indicates whether detailed information about the
   *                          processing should be added to the list.
   *
   * @return  Information about the result of the configuration update.
   * {@inheritDoc}
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
  public ConfigChangeResult applyConfigurationChange(
      SoftReferenceEntryCacheCfg configuration
      )
  {
    // Create a set of variables to use for the result.
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    boolean           configIsAcceptable  = true;
    // Make sure that we can process the defined character sets.  If so, then
    // activate the new configuration.
    boolean applyChanges = false;
    ArrayList<String> errorMessages = new ArrayList<String>();
    EntryCacheCommon.ConfigErrorHandler errorHandler =
      EntryCacheCommon.getConfigErrorHandler (
          EntryCacheCommon.ConfigPhase.PHASE_APPLY, null, errorMessages
          );
    processEntryCacheConfig (configuration, applyChanges, errorHandler);
    // Determine the lock timeout to use when interacting with the lock manager.
    long newLockTimeout = LockManager.DEFAULT_TIMEOUT;
    int msgID = MSGID_SOFTREFCACHE_DESCRIPTION_LOCK_TIMEOUT;
    IntegerWithUnitConfigAttribute lockTimeoutStub =
         new IntegerWithUnitConfigAttribute(ATTR_SOFTREFCACHE_LOCK_TIMEOUT,
                                            getMessage(msgID), false, timeUnits,
                                            true, 0, false, 0);
    try
    {
      IntegerWithUnitConfigAttribute lockTimeoutAttr =
             (IntegerWithUnitConfigAttribute)
             configEntry.getConfigAttribute(lockTimeoutStub);
      if (lockTimeoutAttr != null)
      {
        newLockTimeout = lockTimeoutAttr.pendingCalculatedValue();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_SOFTREFCACHE_INVALID_LOCK_TIMEOUT;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
      }
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be included in the cache.
    HashSet<SearchFilter> newIncludeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_SOFTREFCACHE_DESCRIPTION_INCLUDE_FILTERS;
    StringConfigAttribute includeStub =
         new StringConfigAttribute(ATTR_SOFTREFCACHE_INCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute includeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(includeStub);
      if (includeAttr != null)
      {
        List<String> filterStrings = includeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no include filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              newIncludeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_SOFTREFCACHE_INVALID_INCLUDE_FILTER;
              messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                      filterString, getExceptionMessage(e)));
              if (resultCode == ResultCode.SUCCESS)
              {
                resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
              }
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_SOFTREFCACHE_INVALID_INCLUDE_FILTERS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
      }
      configIsAcceptable = false;
    }
    // Determine the set of cache filters that can be used to control the
    // entries that should be exclude from the cache.
    HashSet<SearchFilter> newExcludeFilters = new HashSet<SearchFilter>();
    msgID = MSGID_SOFTREFCACHE_DESCRIPTION_EXCLUDE_FILTERS;
    StringConfigAttribute excludeStub =
         new StringConfigAttribute(ATTR_SOFTREFCACHE_EXCLUDE_FILTER,
                                   getMessage(msgID), false, true, false);
    try
    {
      StringConfigAttribute excludeAttr =
           (StringConfigAttribute) configEntry.getConfigAttribute(excludeStub);
      if (excludeAttr != null)
      {
        List<String> filterStrings = excludeAttr.activeValues();
        if ((filterStrings == null) || filterStrings.isEmpty())
        {
          // There are no exclude filters, so we'll allow anything by default.
        }
        else
        {
          for (String filterString : filterStrings)
          {
            try
            {
              newExcludeFilters.add(
                   SearchFilter.createFilterFromString(filterString));
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              // We couldn't decode this filter, so it isn't valid.
              msgID = MSGID_SOFTREFCACHE_INVALID_EXCLUDE_FILTER;
              messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                                      filterString, getExceptionMessage(e)));
              if (resultCode == ResultCode.SUCCESS)
              {
                resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
              }
              configIsAcceptable = false;
            }
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // An error occurred, so the provided value must not be valid.
      msgID = MSGID_SOFTREFCACHE_INVALID_EXCLUDE_FILTERS;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              getExceptionMessage(e)));
      if (resultCode == ResultCode.SUCCESS)
      {
        resultCode = ResultCode.CONSTRAINT_VIOLATION;
      }
      configIsAcceptable = false;
    }
    if (configIsAcceptable)
    {
      if (lockTimeout != newLockTimeout)
      {
        lockTimeout = newLockTimeout;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_SOFTREFCACHE_UPDATED_LOCK_TIMEOUT,
                                  lockTimeout));
        }
      }
      if (!includeFilters.equals(newIncludeFilters))
      {
        includeFilters = newIncludeFilters;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_SOFTREFCACHE_UPDATED_INCLUDE_FILTERS));
        }
      }
      if (!excludeFilters.equals(newExcludeFilters))
      {
        excludeFilters = newExcludeFilters;
        if (detailedResults)
        {
          messages.add(getMessage(MSGID_SOFTREFCACHE_UPDATED_EXCLUDE_FILTERS));
        }
      }
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    boolean adminActionRequired = false;
    ConfigChangeResult changeResult = new ConfigChangeResult(
        errorHandler.getResultCode(),
        adminActionRequired,
        errorHandler.getErrorMessages()
        );
    return changeResult;
  }
  /**
   * Parses the provided configuration and configure the entry cache.
   *
   * @param configuration  The new configuration containing the changes.
   * @param applyChanges   If true then take into account the new configuration.
   * @param errorHandler   An handler used to report errors.
   *
   * @return  The mapping between strings of character set values and the
   *          minimum number of characters required from those sets.
   */
  public boolean processEntryCacheConfig(
      SoftReferenceEntryCacheCfg          configuration,
      boolean                             applyChanges,
      EntryCacheCommon.ConfigErrorHandler errorHandler
      )
  {
    // Local variables to read configuration.
    DN                    newConfigEntryDN;
    long                  newLockTimeout;
    HashSet<SearchFilter> newIncludeFilters = null;
    HashSet<SearchFilter> newExcludeFilters = null;
    // Read configuration.
    newConfigEntryDN = configuration.dn();
    newLockTimeout   = configuration.getLockTimeout();
    // Get include and exclude filters.
    switch (errorHandler.getConfigPhase())
    {
    case PHASE_INIT:
      newIncludeFilters = EntryCacheCommon.getFilters (
          configuration.getIncludeFilter(),
          MSGID_SOFTREFCACHE_INVALID_INCLUDE_FILTER,
          MSGID_SOFTREFCACHE_CANNOT_DECODE_ANY_INCLUDE_FILTERS,
          errorHandler,
          configEntryDN
          );
      newExcludeFilters = EntryCacheCommon.getFilters (
          configuration.getExcludeFilter(),
          MSGID_SOFTREFCACHE_CANNOT_DECODE_EXCLUDE_FILTER,
          MSGID_SOFTREFCACHE_CANNOT_DECODE_ANY_EXCLUDE_FILTERS,
          errorHandler,
          configEntryDN
          );
      break;
    case PHASE_ACCEPTABLE:  // acceptable and apply are using the same
    case PHASE_APPLY:       // error ID codes
      newIncludeFilters = EntryCacheCommon.getFilters (
          configuration.getIncludeFilter(),
          MSGID_SOFTREFCACHE_INVALID_INCLUDE_FILTER,
          0,
          errorHandler,
          configEntryDN
          );
      newExcludeFilters = EntryCacheCommon.getFilters (
          configuration.getExcludeFilter(),
          MSGID_SOFTREFCACHE_INVALID_EXCLUDE_FILTER,
          0,
          errorHandler,
          configEntryDN
          );
      break;
    }
    if (applyChanges && errorHandler.getIsAcceptable())
    {
      configEntryDN  = newConfigEntryDN;
      lockTimeout    = newLockTimeout;
      includeFilters = newIncludeFilters;
      excludeFilters = newExcludeFilters;
    }
    return errorHandler.getIsAcceptable();
  }
  /**
   * Operate in a loop, receiving notification of soft references that have been
   * freed and removing the corresponding entries from the cache.
   */
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/Base64PasswordStorageSchemeTestCase.java
@@ -28,6 +28,9 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -62,7 +65,14 @@
         throws Exception
  {
    Base64PasswordStorageScheme scheme = new Base64PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ClearPasswordStorageSchemeTestCase.java
@@ -28,6 +28,9 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -62,7 +65,14 @@
         throws Exception
  {
    ClearPasswordStorageScheme scheme = new ClearPasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java
@@ -36,6 +36,11 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.
       ErrorLogAccountStatusNotificationHandlerCfgDefn;
import org.opends.server.admin.std.server.
       ErrorLogAccountStatusNotificationHandlerCfg;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
@@ -149,7 +154,12 @@
    ErrorLogAccountStatusNotificationHandler handler =
         new ErrorLogAccountStatusNotificationHandler();
    handler.initializeStatusNotificationHandler(configEntry);
    ErrorLogAccountStatusNotificationHandlerCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          ErrorLogAccountStatusNotificationHandlerCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    handler.initializeStatusNotificationHandler(configuration);
  }
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/MD5PasswordStorageSchemeTestCase.java
@@ -28,6 +28,9 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -62,7 +65,14 @@
         throws Exception
  {
    MD5PasswordStorageScheme scheme = new MD5PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SHA1PasswordStorageSchemeTestCase.java
@@ -28,6 +28,9 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -62,7 +65,14 @@
         throws Exception
  {
    SHA1PasswordStorageScheme scheme = new SHA1PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedMD5PasswordStorageSchemeTestCase.java
@@ -28,6 +28,11 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.ErrorLogAccountStatusNotificationHandlerCfgDefn;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.ErrorLogAccountStatusNotificationHandlerCfg;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -63,7 +68,14 @@
  {
    SaltedMD5PasswordStorageScheme scheme =
         new SaltedMD5PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA1PasswordStorageSchemeTestCase.java
@@ -30,6 +30,9 @@
import org.testng.annotations.Test;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.schema.UserPasswordSyntax;
@@ -70,7 +73,14 @@
  {
    SaltedSHA1PasswordStorageScheme scheme =
         new SaltedSHA1PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
@@ -89,7 +99,14 @@
  {
    SaltedSHA1PasswordStorageScheme scheme =
         new SaltedSHA1PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    String passwordString = scheme.encodeOffline(plaintext.value());
    String[] pwComps = UserPasswordSyntax.decodeUserPassword(passwordString);
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA256PasswordStorageSchemeTestCase.java
@@ -28,6 +28,9 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -63,7 +66,14 @@
  {
    SaltedSHA256PasswordStorageScheme scheme =
         new SaltedSHA256PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA384PasswordStorageSchemeTestCase.java
@@ -28,6 +28,9 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -63,7 +66,14 @@
  {
    SaltedSHA384PasswordStorageScheme scheme =
         new SaltedSHA384PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SaltedSHA512PasswordStorageSchemeTestCase.java
@@ -28,6 +28,9 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
@@ -63,7 +66,14 @@
  {
    SaltedSHA512PasswordStorageScheme scheme =
         new SaltedSHA512PasswordStorageScheme();
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AuthPasswordEqualityMatchingRuleTest.java
@@ -26,6 +26,9 @@
 */
package org.opends.server.schema;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.EqualityMatchingRule;
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.DirectoryServer;
@@ -73,8 +76,16 @@
    SaltedMD5PasswordStorageScheme scheme = new SaltedMD5PasswordStorageScheme();
    
    ConfigEntry configEntry =
       DirectoryServer.getConfigEntry(DN.decode("cn=Salted MD5,cn=Password Storage Schemes,cn=config"));
    scheme.initializePasswordStorageScheme(configEntry);
       DirectoryServer.getConfigEntry(
           DN.decode("cn=Salted MD5,cn=Password Storage Schemes,cn=config"));
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    
    ByteString encodedAuthPassword = scheme.encodeAuthPassword(bytePassword);
    StringBuilder[] authPWComponents =
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/UserPasswordEqualityMatchingRuleTest.java
@@ -26,6 +26,9 @@
 */
package org.opends.server.schema;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
import org.opends.server.api.EqualityMatchingRule;
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.DirectoryServer;
@@ -74,7 +77,14 @@
    ConfigEntry configEntry =
       DirectoryServer.getConfigEntry(
           DN.decode("cn=Salted MD5,cn=Password Storage Schemes,cn=config"));
    scheme.initializePasswordStorageScheme(configEntry);
    PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
          PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    
    ByteString encodedAuthPassword =
         scheme.encodePasswordWithScheme(bytePassword);