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

boli
27.58.2007 ffb9044301d1c169f934e0adf4f473e99da39a47
This adds the numSubordinates and hasSubordinates operational attribute support in OpenDS.
- Implemented as virtual attributes
- They are enabled by default
- numSubordinates and hasSubordinates methods added to the backend API and implemented for all existing backends
- JE implementation uses the id2children index to keep count of the number of subordinates for each entry.
- The behavior of exceeding the index-entry-limit (ALL-IDs) has changed to store a 8 byte entry ID set count with the most significant bit
set to 1 instead of a 0 byte array to signify the index-entry-limit has been exceeded. The previous format is still compatible but all requests
for numSubordinates will return undefined (-1).
- The DBTest tool is also included in this fix. This can be used to list root containers, entry containers, database containers, index
status, as well as dumping a database with or without decoding the data.

Fix for issues 43 and 72
7 files added
22 files modified
4602 ■■■■■ changed files
opends/resource/bin/dbtest 37 ●●●●● patch | view | raw | blame | history
opends/resource/bin/dbtest.bat 33 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif 18 ●●●●● patch | view | raw | blame | history
opends/resource/schema/00-core.ldif 9 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/extension.properties 4 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/tools.properties 79 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/Backend.java 35 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/BackupBackend.java 116 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MemoryBackend.java 60 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MonitorBackend.java 89 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/RootDSEBackend.java 76 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java 75 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 101 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java 11 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java 26 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java 112 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/Index.java 21 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java 32 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/JebFormat.java 74 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskBackend.java 81 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskScheduler.java 40 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java 54 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProvider.java 279 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java 250 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/DBTest.java 1602 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/resource/config-changes.ldif 14 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java 48 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProviderTestCase.java 581 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProviderTestCase.java 645 ●●●●● patch | view | raw | blame | history
opends/resource/bin/dbtest
New file
@@ -0,0 +1,37 @@
#!/bin/sh
#
# 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.
# This script may be used to debug JE backends in the Directory Server.
OPENDS_INVOKE_CLASS="org.opends.server.tools.DBTest"
export OPENDS_INVOKE_CLASS
SCRIPT_NAME_ARG="-Dorg.opends.server.scriptName=dbtest"
export SCRIPT_NAME_ARG
SCRIPT_DIR=`dirname "${0}"`
"${SCRIPT_DIR}/../lib/_server-script.sh" "${@}"
opends/resource/bin/dbtest.bat
New file
@@ -0,0 +1,33 @@
@echo off
rem CDDL HEADER START
rem
rem The contents of this file are subject to the terms of the
rem Common Development and Distribution License, Version 1.0 only
rem (the "License").  You may not use this file except in compliance
rem with the License.
rem
rem You can obtain a copy of the license at
rem trunk/opends/resource/legal-notices/OpenDS.LICENSE
rem or https://OpenDS.dev.java.net/OpenDS.LICENSE.
rem See the License for the specific language governing permissions
rem and limitations under the License.
rem
rem When distributing Covered Code, include this CDDL HEADER in each
rem file and include the License file at
rem trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
rem add the following below this CDDL HEADER, with the fields enclosed
rem by brackets "[]" replaced with your own identifying information:
rem      Portions Copyright [yyyy] [name of copyright owner]
rem
rem CDDL HEADER END
rem
rem
rem      Portions Copyright 2006-2007 Sun Microsystems, Inc.
setlocal
set OPENDS_INVOKE_CLASS="org.opends.server.tools.DBTest"
set SCRIPT_NAME_ARG="-Dorg.opends.server.scriptName=dbtest"
for %%i in (%~sf0) do call "%%~dPsi\..\lib\_server-script.bat" %*
opends/resource/config/config.ldif
@@ -1948,6 +1948,24 @@
ds-cfg-virtual-attribute-filter: (&(objectClass=groupOfUniqueNames)(objectClass=ds-virtual-static-group))
ds-cfg-allow-retrieving-membership: false
dn: cn=numSubordinates,cn=Virtual Attributes,cn=config
objectClass: top
objectClass: ds-cfg-virtual-attribute
cn: numSubordinates
ds-cfg-virtual-attribute-class: org.opends.server.extensions.NumSubordinatesVirtualAttributeProvider
ds-cfg-virtual-attribute-enabled: true
ds-cfg-virtual-attribute-type: numSubordinates
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
dn: cn=hasSubordinates,cn=Virtual Attributes,cn=config
objectClass: top
objectClass: ds-cfg-virtual-attribute
cn: hasSubordinates
ds-cfg-virtual-attribute-class: org.opends.server.extensions.HasSubordinatesVirtualAttributeProvider
ds-cfg-virtual-attribute-enabled: true
ds-cfg-virtual-attribute-type: hasSubordinates
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
dn: cn=Work Queue,cn=config
objectClass: top
objectClass: ds-cfg-work-queue
opends/resource/schema/00-core.ldif
@@ -176,6 +176,9 @@
attributeTypes: ( 2.5.18.4 NAME 'modifiersName' EQUALITY distinguishedNameMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION
  USAGE directoryOperation X-ORIGIN 'RFC 4512' )
attributeTypes: ( 2.5.18.9 NAME 'hasSubordinates' EQUALITY booleanMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE NO-USER-MODIFICATION
  USAGE directoryOperation X-ORIGIN 'X.501' )
attributeTypes: ( 2.5.18.10 NAME 'subschemaSubentry'
  EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation
@@ -434,6 +437,12 @@
  DESC 'DN of the entry' EQUALITY distinguishedNameMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION
  USAGE directoryOperation X-ORIGIN 'draft-zeilenga-ldap-entrydn' )
attributeTypes: ( 1.3.6.1.4.1.453.16.2.103 NAME 'numSubordinates'
  DESC 'Count of immediate subordinates'
  EQUALITY integerMatch ORDERING integerOrderingMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation
  X-ORIGIN 'draft-ietf-boreham-numsubordinates' )
objectClasses: ( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass
  X-ORIGIN 'RFC 4512' )
objectClasses: ( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName
opends/src/messages/messages/extension.properties
@@ -1729,3 +1729,7 @@
 included in the nested group list for the group
MILD_ERR_STATICGROUP_GROUP_INSTANCE_INVALID_540=Group instance with DN %s has \
 been deleted and is no longer valid
MILD_ERR_NUMSUBORDINATES_VATTR_NOT_SEARCHABLE_541=The %s attribute is not \
 searchable and should not be included in otherwise unindexed search filters
MILD_ERR_HASSUBORDINATES_VATTR_NOT_SEARCHABLE_542=The %s attribute is not \
 searchable and should not be included in otherwise unindexed search filters
opends/src/messages/messages/tools.properties
@@ -2217,3 +2217,82 @@
 generate the RC script:  %s
SEVERE_ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE_1326=If you specify \
 the {%s} argument you must also specify {%s}
INFO_DESCRIPTION_DBTEST_TOOL_1327=This utility may be used to debug the JE \
  database
INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_ROOT_CONTAINERS_1328=List the root \
  containers used by all JE backends
INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_ENTRY_CONTAINERS_1329=List the entry \
  containers for a root container
INFO_DESCRIPTION_DBTEST_SUBCMD_DUMP_DATABASE_CONTAINER_1330=Dump records from \
  a database container
INFO_DESCRIPTION_DBTEST_BACKEND_ID_1331=The backend ID of the JE backend to \
  debug
INFO_DESCRIPTION_DBTEST_BASE_DN_1332=The base DN of the entry container to debug
INFO_DESCRIPTION_DBTEST_DATABASE_NAME_1333=The name of the database container \
  to debug
INFO_DESCRIPTION_DBTEST_SKIP_DECODE_1334=Do not try to decode the JE data to \
  their appropreate types
MILD_ERR_DBTEST_DECODE_FAIL_1335=An error occured while decoding data: %s
INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_INDEX_STATUS_1336=List the status of \
  indexes in a entry container
INFO_DESCRIPTION_DBTEST_MAX_KEY_VALUE_1337=Only show records with keys that \
  should be ordered before the provided value using the comparator for the \
  database container
INFO_DESCRIPTION_DBTEST_MIN_KEY_VALUE_1338=Only show records with keys that \
  should be ordered after the provided value using the comparator for the \
  database container
INFO_DESCRIPTION_DBTEST_MAX_DATA_SIZE_1339=Only show records whose data is no \
  larger than the provided value
INFO_DESCRIPTION_DBTEST_MIN_DATA_SIZE_1340=Only show records whose data is no \
  smaller than the provided value
INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_DATABASE_CONTAINERS_1341=List the database \
  containers for a entry container
INFO_LABEL_DBTEST_BACKEND_ID_1342=Backend ID
INFO_LABEL_DBTEST_DB_DIRECTORY_1343=Database Directory
INFO_LABEL_DBTEST_BASE_DN_1344=Base DN
INFO_LABEL_DBTEST_JE_DATABASE_PREFIX_1345=JE Database Prefix
INFO_LABEL_DBTEST_ENTRY_COUNT_1346=Entry Count
SEVERE_ERR_DBTEST_NO_BACKENDS_FOR_ID_1347=None of the Directory Server \
  backends are configured with the requested backend ID %s
SEVERE_ERR_DBTEST_NO_ENTRY_CONTAINERS_FOR_BASE_DN_1348=None of the entry \
  containers are configured with the requested base DN %s in backend %s
SEVERE_ERR_DBTEST_NO_DATABASE_CONTAINERS_FOR_NAME_1349=No database container \
  exists with the requested name %s in entry container %s and backend %s
SEVERE_ERR_DBTEST_ERROR_INITIALIZING_BACKEND_1350=An unexpected error occured \
  while attempting to initialize the JE backend %s: %s
SEVERE_ERR_DBTEST_ERROR_READING_DATABASE_1351=An unexpected error occured \
  while attempting to read and/or decode records from the database: %s
SEVERE_ERR_DBTEST_DECODE_BASE_DN_1352=Unable to decode base DN string "%s" as \
  a valid distinguished name:  %s
INFO_LABEL_DBTEST_DATABASE_NAME_1353=Database Name
INFO_LABEL_DBTEST_DATABASE_TYPE_1354=Database Type
INFO_LABEL_DBTEST_JE_DATABASE_NAME_1355=JE Database Name
INFO_LABEL_DBTEST_JE_RECORD_COUNT_1356=Record Count
INFO_LABEL_DBTEST_INDEX_NAME_1357=Index Name
INFO_LABEL_DBTEST_INDEX_TYPE_1358=Index Type
INFO_LABEL_DBTEST_INDEX_STATUS_1359=Index Status
INFO_LABEL_DBTEST_KEY_1360=Key
INFO_LABEL_DBTEST_DATA_1361=Data
SEVERE_WARN_DBTEST_CANNOT_UNLOCK_BACKEND_1362=An error occurred while \
 attempting to release the shared lock for backend %s:  %s.  This lock should \
 automatically be cleared when the process exits, so no further action \
 should be required
SEVERE_ERR_DBTEST_CANNOT_LOCK_BACKEND_1363=An error occurred while \
 attempting to acquire a shared lock for backend %s:  %s.  This generally \
 means that some other process has exclusive access to this backend (e.g., a \
 restore or an LDIF import)
SEVERE_ERR_DBTEST_CANNOT_DECODE_KEY_1364=An error occured while decoding the \
  min/max key value %s: %s. Values prefixed with "0x" will be decoded as raw \
  bytes in hex. When dumping the DN2ID database, the value must be a valid \
  distinguished name. When dumping the ID2Entry database, the value will be \
  decoded as a entry ID. When dumping all other databases, the value will be \
  decoded as a string
INFO_LABEL_DBTEST_ENTRY_1365=Entry
INFO_LABEL_DBTEST_ENTRY_ID_1366=Entry ID
INFO_LABEL_DBTEST_ENTRY_DN_1367=Entry DN
INFO_LABEL_DBTEST_URI_1368=URI
INFO_LABEL_DBTEST_INDEX_VALUE_1369=Indexed Value
INFO_LABEL_DBTEST_INDEX_ENTRY_ID_LIST_1370=Entry ID List
INFO_LABEL_DBTEST_VLV_INDEX_LAST_SORT_KEYS_1371=Last Sort Keys
SEVERE_ERR_DBTEST_CANNOT_DECODE_SIZE_1372=An error occured while parsing the \
  min/max data size %s as a integer: %s
opends/src/server/org/opends/server/api/Backend.java
@@ -57,6 +57,7 @@
import org.opends.server.types.LockManager;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.WritabilityMode;
import org.opends.server.types.ConditionResult;
import static org.opends.messages.BackendMessages.*;
@@ -238,6 +239,40 @@
  /**
   * Indicates whether the requested entry has any subordinates.
   *
   * @param entryDN The distinguished name of the entry.
   *
   * @return {@code ConditionResult.TRUE} if the entry has one or more
   *         subordinates or {@code ConditionResult.FALSE} otherwise
   *         or {@code ConditionResult.UNDEFINED} if it can not be
   *         determined.
   *
   * @throws DirectoryException  If a problem occurs while trying to
   *                              retrieve the entry.
   */
  public abstract ConditionResult hasSubordinates(DN entryDN)
        throws DirectoryException;
  /**
   * Retrieves the number of subordinates for the requested entry.
   *
   * @param entryDN The distinguished name of the entry.
   *
   * @return The number of subordinate entries for the requested entry
   *         or -1 if it can not be determined.
   *
   * @throws DirectoryException  If a problem occurs while trying to
   *                              retrieve the entry.
   */
  public abstract long numSubordinates(DN entryDN)
      throws DirectoryException;
  /**
   * Indicates whether an entry with the specified DN exists in the
   * backend. The default implementation obtains a read lock and calls
   * {@code getEntry}, but backend implementations may override this
opends/src/server/org/opends/server/backends/BackupBackend.java
@@ -43,31 +43,11 @@
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.schema.BooleanSyntax;
import org.opends.server.schema.GeneralizedTimeSyntax;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.BackupInfo;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.RDN;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.server.util.ServerConstants.*;
@@ -341,7 +321,101 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    // If the requested entry was null, then return undefined.
    if (entryDN == null)
    {
      return -1;
    }
    // If the requested entry was the backend base entry, then return
    // the number of backup directories.
    if (backupBaseDN.equals(entryDN))
    {
      long count = 0;
      for (File f : backupDirectories)
      {
        // Check to see if the descriptor file exists.  If not, then skip this
        // backup directory.
        File descriptorFile = new File(f, BACKUP_DIRECTORY_DESCRIPTOR_FILE);
        if (! descriptorFile.exists())
        {
          continue;
        }
        count ++;
      }
      return count;
    }
    // See if the requested entry was one level below the backend base entry.
    // If so, then it must point to a backup directory.  Otherwise, it must be
    // two levels below the backup base entry and must point to a specific
    // backup.
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null)
    {
      return -1;
    }
    else if (backupBaseDN.equals(parentDN))
    {
      long count = 0;
      Entry backupDirEntry = getBackupDirectoryEntry(entryDN);
      AttributeType t =
          DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
      List<Attribute> attrList = backupDirEntry.getAttribute(t);
      if ((attrList != null) && (! attrList.isEmpty()))
      {
        for (AttributeValue v : attrList.get(0).getValues())
        {
          try
          {
            BackupDirectory backupDirectory =
                BackupDirectory.readBackupDirectoryDescriptor(
                    v.getStringValue());
            count += backupDirectory.getBackups().keySet().size();
          }
          catch (Exception e)
          {
            return -1;
          }
        }
      }
      return count;
    }
    else if (backupBaseDN.equals(parentDN.getParentDNInSuffix()))
    {
      return 0;
    }
    else
    {
      return -1;
    }
  }
  /**
   * Retrieves the requested entry from this backend.
opends/src/server/org/opends/server/backends/MemoryBackend.java
@@ -28,11 +28,7 @@
import org.opends.messages.Message;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.*;
import org.opends.server.api.Backend;
import org.opends.server.config.ConfigException;
@@ -42,20 +38,6 @@
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.Control;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SearchFilter;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.LDIFWriter;
@@ -63,7 +45,7 @@
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -291,7 +273,45 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    // Try to look up the immediate children for the DN
    Set<DN> children = childDNs.get(entryDN);
    if (children == null)
    {
      if(entryMap.get(entryDN) != null)
      {
        // The entry does exist but just no children.
        return 0;
      }
      return -1;
    }
    return children.size();
  }
  /**
   * {@inheritDoc}
opends/src/server/org/opends/server/backends/MonitorBackend.java
@@ -48,25 +48,6 @@
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.RDN;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.util.DynamicConstants;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.TimeThread;
@@ -75,7 +56,7 @@
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.util.ServerConstants.*;
@@ -355,7 +336,75 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    // If the requested entry was null, then return undefined.
    if (entryDN == null)
    {
      return -1;
    }
    // If the requested entry was the monitor base entry, then return
    // the number of monitor providers.
    if (entryDN.equals(baseMonitorDN))
    {
      return DirectoryServer.getMonitorProviders().size();
    }
    // See if the monitor base entry is the immediate parent for the requested
    // entry.  If not, then its undefined.
    DN parentDN = entryDN.getParentDNInSuffix();
    if ((parentDN == null) || (! parentDN.equals(baseMonitorDN)))
    {
      return -1;
    }
    // Get the RDN for the requested DN and make sure it is single-valued.
    RDN entryRDN = entryDN.getRDN();
    if (entryRDN.isMultiValued())
    {
      return -1;
    }
    // Get the RDN value and see if it matches the instance name for one of
    // the directory server monitor providers.
    String rdnValue = entryRDN.getAttributeValue(0).getStringValue();
    MonitorProvider<? extends MonitorProviderCfg> monitorProvider =
         DirectoryServer.getMonitorProvider(rdnValue.toLowerCase());
    if (monitorProvider == null)
    {
      return -1;
    }
    return 0;
  }
  /**
   * Retrieves the requested entry from this backend.
opends/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -53,27 +53,7 @@
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.CancelRequest;
import org.opends.server.types.CancelResult;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.*;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.Validator;
@@ -401,7 +381,61 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    if (entryDN == null || ! entryDN.isNullDN())
    {
      return -1;
    }
    long count = 0;
    Map<DN,Backend> baseMap;
    if (subordinateBaseDNs == null)
    {
      baseMap = DirectoryServer.getPublicNamingContexts();
    }
    else
    {
      baseMap = subordinateBaseDNs;
    }
    for (DN subBase : baseMap.keySet())
    {
      Backend b = baseMap.get(subBase);
      Entry subBaseEntry = b.getEntry(subBase);
      if (subBaseEntry != null)
      {
        count++;
      }
    }
    return count;
  }
  /**
   * Retrieves the requested entry from this backend.
opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -76,42 +76,11 @@
import org.opends.server.schema.MatchingRuleUseSyntax;
import org.opends.server.schema.NameFormSyntax;
import org.opends.server.schema.ObjectClassSyntax;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.BackupInfo;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.CryptoManager;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DITContentRule;
import org.opends.server.types.DITStructureRule;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ExistingFileBehavior;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.MatchingRuleUse;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.NameForm;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ObjectClassType;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.Schema;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.util.DynamicConstants;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.Validator;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
@@ -626,7 +595,49 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    boolean found = false;
    for (DN dn : baseDNs)
    {
      if (dn.equals(entryDN))
      {
        found = true;
        break;
      }
    }
    if (! found)
    {
      return -1;
    }
    return 0;
  }
  /**
   * Retrieves the requested entry from this backend.
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -311,7 +311,7 @@
      EnvironmentConfig envConfig =
          ConfigurableEnvironment.parseConfigEntry(cfg);
      initializeRootContainer(envConfig);
      rootContainer = initializeRootContainer(envConfig);
    }
    // Preload the database cache.
@@ -598,7 +598,63 @@
    return -1;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    EntryContainer ec = rootContainer.getEntryContainer(entryDN);
    if(ec == null)
    {
      return -1;
    }
    readerBegin();
    ec.sharedLock.lock();
    try
    {
      long count = ec.getNumSubordinates(entryDN);
      if(count == Long.MAX_VALUE)
      {
        // The index entry limit has exceeded and there is no count maintained.
        return -1;
      }
      return count;
    }
    catch (DatabaseException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    finally
    {
      ec.sharedLock.unlock();
      readerEnd();
    }
  }
  /**
   * Retrieves the requested entry from this backend.  Note that the caller must
@@ -938,7 +994,7 @@
        envConfig.setConfigParam("je.env.isLocking", "true");
        envConfig.setConfigParam("je.env.runCheckpointer", "true");
        initializeRootContainer(envConfig);
        rootContainer = initializeRootContainer(envConfig);
      }
@@ -1081,7 +1137,7 @@
        envConfig.setConfigParam("je.env.runCheckpointer", "false");
      }
      initializeRootContainer(envConfig);
      rootContainer = initializeRootContainer(envConfig);
      ImportJob importJob = new ImportJob(importConfig);
      return importJob.importLDIF(rootContainer);
@@ -1192,7 +1248,7 @@
        envConfig.setConfigParam("je.env.isLocking", "true");
        envConfig.setConfigParam("je.env.runCheckpointer", "true");
        initializeRootContainer(envConfig);
        rootContainer = initializeRootContainer(envConfig);
      }
      VerifyJob verifyJob = new VerifyJob(verifyConfig);
@@ -1275,7 +1331,7 @@
        EnvironmentConfig envConfig =
            ConfigurableEnvironment.parseConfigEntry(cfg);
        initializeRootContainer(envConfig);
        rootContainer = initializeRootContainer(envConfig);
      }
      RebuildJob rebuildJob = new RebuildJob(rebuildConfig);
@@ -1502,6 +1558,34 @@
  }
  /**
   * Returns a new read-only handle to the JE root container for this backend.
   * The caller is responsible for closing the root container after use.
   *
   * @return The read-only RootContainer object for this backend.
   *
   * @throws  ConfigException  If an unrecoverable problem arises during
   *                           initialization.
   * @throws  InitializationException  If a problem occurs during initialization
   *                                   that is not related to the server
   *                                   configuration.
   */
  public RootContainer getReadOnlyRootContainer()
      throws ConfigException, InitializationException
  {
    EnvironmentConfig envConfig =
        ConfigurableEnvironment.parseConfigEntry(cfg);
    envConfig.setReadOnly(true);
    envConfig.setAllowCreate(false);
    envConfig.setTransactional(false);
    envConfig.setTxnNoSync(false);
    envConfig.setConfigParam("je.env.isLocking", "true");
    envConfig.setConfigParam("je.env.runCheckpointer", "true");
    return initializeRootContainer(envConfig);
  }
  /**
   * Clears all the entries from the backend.  This method is for test cases
   * that use the JE backend.
   *
@@ -1589,14 +1673,15 @@
    return cfg.dn();
  }
  private void initializeRootContainer(EnvironmentConfig envConfig)
  private RootContainer initializeRootContainer(EnvironmentConfig envConfig)
      throws ConfigException, InitializationException
  {
    // Open the database environment
    try
    {
      rootContainer = new RootContainer(this, cfg);
      rootContainer.open(envConfig);
      RootContainer rc = new RootContainer(this, cfg);
      rc.open(envConfig);
      return rc;
    }
    catch (DatabaseException e)
    {
opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java
@@ -142,15 +142,16 @@
    {
      if (entryLimit > 0 && entryIDList.size() >= entryLimit)
      {
        bufferedValue.value = new EntryIDSet();
        entryIDList = new EntryIDSet(entryIDList.size());
        entryIDList.add(entryID);
        bufferedValue.value = entryIDList;
        bufferedValue.isDirty = true;
        return;
      }
      else
      {
    }
        bufferedValue.isDirty = entryIDList.add(entryID);
      }
    }
  }
  /**
   * Remove an ID from the given key.
opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -704,6 +704,32 @@
  }
  /**
   * Determine the number of subordinate entries for a given entry.
   *
   * @param entryDN The distinguished name of the entry.
   * @return The number of subordinate entries for the given entry or -1 if
   *         the entry does not exist.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public long getNumSubordinates(DN entryDN) throws DatabaseException
  {
    EntryID entryID = dn2id.get(null, entryDN);
    if (entryID != null)
    {
      DatabaseEntry key =
          new DatabaseEntry(JebFormat.entryIDToDatabase(entryID.longValue()));
      EntryIDSet entryIDSet =
          id2children.readKey(key, null, LockMode.DEFAULT);
      long count = entryIDSet.size();
      if(count != Long.MAX_VALUE)
      {
        return count;
      }
    }
    return -1;
  }
  /**
   * Processes the specified search in this entryContainer.
   * Matching entries should be provided back to the core server using the
   * <CODE>SearchOperation.returnEntry</CODE> method.
opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java
@@ -45,6 +45,12 @@
  private long[] values = null;
  /**
   * The size of the set when it is not defined. This value is only maintained
   * when the set is undefined.
   */
  private long undefinedSize = Long.MAX_VALUE;
  /**
   * The database key containing this set, if the set was constructed
   * directly from the database.
   */
@@ -56,6 +62,18 @@
  public EntryIDSet()
  {
    values = null;
    undefinedSize = Long.MAX_VALUE;
  }
  /**
   * Create a new undefined set with a initial size.
   *
   * @param size The undefined size for this set.
   */
  public EntryIDSet(long size)
  {
    values = null;
    undefinedSize = size;
  }
  /**
@@ -74,9 +92,27 @@
      return;
    }
    if (bytes.length == 0)
    {
      // Entry limit has exceeded and there is no encoded undefined set size.
      values = null;
      undefinedSize = Long.MAX_VALUE;
    }
    else if ((bytes[0] & 0x80) == 0x80)
    {
      // Entry limit has exceeded and there is an encoded undefined set size.
      values = null;
      undefinedSize = JebFormat.entryIDUndefinedSizeFromDatabase(bytes);
    }
    else
    {
      // Seems like entry limit has not been exceeded and the bytes is a
      // list of entry IDs.
    values = JebFormat.entryIDListFromDatabase(bytes);
  }
  }
  /**
   * Construct an EntryIDSet from an array of longs.
   *
@@ -105,15 +141,28 @@
    boolean needSort = false;
    int count = 0;
    boolean undefined = false;
    for (EntryIDSet l : sets)
    {
      if (!l.isDefined())
      {
        if(l.undefinedSize == Long.MAX_VALUE)
        {
        return new EntryIDSet();
      }
        else
        {
          undefined = true;
        }
      }
      count += l.size();
    }
    if(undefined)
    {
      return new EntryIDSet(count);
    }
    long[] n = new long[count];
    int pos = 0;
    for (EntryIDSet l : sets)
@@ -165,11 +214,11 @@
   *
   * @return The number of IDs in the set.
   */
  public int size()
  public long size()
  {
    if (values == null)
    {
      return Integer.MAX_VALUE;
      return undefinedSize;
    }
    else
    {
@@ -200,10 +249,19 @@
      if (keyBytes != null)
      {
        // The index entry limit was exceeded
        if(undefinedSize == Long.MAX_VALUE)
        {
        buffer.append("[LIMIT-EXCEEDED]");
      }
      else
      {
          buffer.append("[LIMIT-EXCEEDED:");
          buffer.append(undefinedSize);
          buffer.append("]");
        }
      }
      else
      {
        // Not indexed
        buffer.append("[NOT-INDEXED]");
      }
@@ -232,8 +290,15 @@
   */
  public byte[] toDatabase()
  {
    if(isDefined())
    {
    return JebFormat.entryIDListToDatabase(values);
  }
    else
    {
      return JebFormat.entryIDUndefinedSizeToDatabase(undefinedSize);
    }
  }
  /**
   * Insert an ID into this set.
@@ -246,7 +311,11 @@
  {
    if (values == null)
    {
      return false;
      if(undefinedSize != Long.MAX_VALUE)
      {
        undefinedSize++;
      }
      return true;
    }
    long id = entryID.longValue();
@@ -299,7 +368,11 @@
  {
    if (values == null)
    {
      return false;
      if(undefinedSize != Long.MAX_VALUE)
      {
        undefinedSize--;
      }
      return true;
    }
    if (values.length == 0)
@@ -376,6 +449,7 @@
    if (!this.isDefined())
    {
      this.values = that.values;
      this.undefinedSize = that.undefinedSize;
      return;
    }
@@ -430,11 +504,26 @@
  {
    if (!this.isDefined())
    {
      // Can't simply add the undefined size of this set to that set since
      // we don't know if there are any duplicates. In this case, we can't
      // maintain the undefined size anymore.
      if(undefinedSize != Long.MAX_VALUE && that.size() > 0)
      {
        undefinedSize = Long.MAX_VALUE;
      }
      return;
    }
    if (!that.isDefined())
    {
      if(that.size() == 0)
      {
        undefinedSize = values.length;
      }
      else
      {
        undefinedSize = Long.MAX_VALUE;
      }
      values = null;
      return;
    }
@@ -533,11 +622,26 @@
  {
    if (!this.isDefined())
    {
      // Can't simply subtract the undefined size of this set to that set since
      // we don't know if there are any duplicates. In this case, we can't
      // maintain the undefined size anymore.
      if(undefinedSize != Long.MAX_VALUE && that.size() > 0)
      {
        undefinedSize = Long.MAX_VALUE;
      }
      return;
    }
    if (!that.isDefined())
    {
      if(that.size() == 0)
      {
        undefinedSize = values.length;
      }
      else
      {
        undefinedSize = Long.MAX_VALUE;
      }
      values = null;
      return;
    }
opends/src/server/org/opends/server/backends/jeb/Index.java
@@ -198,7 +198,7 @@
      {
        if (indexEntryLimit > 0 && entryIDList.size() >= indexEntryLimit)
        {
          entryIDList = new EntryIDSet();
          entryIDList = new EntryIDSet(entryIDList.size());
          entryLimitExceededCount++;
          if(debugEnabled())
@@ -212,19 +212,14 @@
          }
        }
        else
        {
          if(!entryIDList.add(entryID))
          {
            success = false;
          }
        }
      success = entryIDList.add(entryID);
        byte[] after = entryIDList.toDatabase();
        data.setData(after);
        put(txn, key, data);
      }
    }
    else
    {
      if(rebuildRunning || trusted)
@@ -256,21 +251,16 @@
    if (status == OperationStatus.SUCCESS)
    {
      EntryIDSet entryIDList = new EntryIDSet(key.getData(), data.getData());
      if (entryIDList.isDefined())
      {
        // Ignore failures if rebuild is running since the entry ID is
        // probably already removed.
        if (!entryIDList.remove(entryID) && !rebuildRunning)
        {
          // Invalidate the key by setting it undefined
          byte[] after = new EntryIDSet().toDatabase();
          data.setData(after);
          put(txn, key, data);
          if(trusted)
          {
            setTrusted(txn, false);
            if(debugEnabled())
            {
              StringBuilder builder = new StringBuilder();
@@ -300,7 +290,6 @@
          }
        }
      }
    }
    else
    {
      // Ignore failures if rebuild is running since a empty entryIDset
opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java
@@ -257,9 +257,32 @@
              if (index.read(txn, dbKey, dbData, LockMode.RMW) ==
                   OperationStatus.SUCCESS)
              {
                if (dbData.getSize() == 0)
                if (dbData.getSize() == 8 &&
                    (dbData.getData()[0] & 0x80) == 0x80)
                {
                  // Entry limit already exceeded.
                  // Entry limit already exceeded. Just update the
                  // undefined size assuming no overlap will occur between
                  // the add values and the longs in the DB.
                  long undefinedSize =
                   JebFormat.entryIDUndefinedSizeFromDatabase(dbData.getData());
                  for(Longs l : addValues)
                  {
                    undefinedSize += l.size();
                  }
                  if(replaceExisting)
                  {
                    for(Longs l : delValues)
                    {
                      undefinedSize -= l.size();
                    }
                  }
                  byte[] undefinedSizeBytes =
                      JebFormat.entryIDUndefinedSizeToDatabase(undefinedSize);
                  dbData.setData(undefinedSizeBytes);
                  index.put(txn, dbKey, dbData);
                  break writeMergedValue;
                }
                merged.decode(dbData.getData());
@@ -281,7 +304,10 @@
            if (merged.size() > entryLimit)
            {
              index.writeKey(txn, dbKey, new EntryIDSet());
              byte[] undefinedSizeBytes =
                  JebFormat.entryIDUndefinedSizeToDatabase(merged.size());
              dbData.setData(undefinedSizeBytes);
              index.put(txn, dbKey, dbData);
            }
            else
            {
opends/src/server/org/opends/server/backends/jeb/JebFormat.java
@@ -301,26 +301,47 @@
  }
  /**
   * Decode an entry ID count from its database representation.
   *
   * @param bytes The database value of the entry ID count.
   * @return The entry ID count.
   */
  public static long entryIDUndefinedSizeFromDatabase(byte[] bytes)
  {
    if(bytes == null)
    {
      return 0;
    }
    if(bytes.length == 8)
    {
      long v = 0;
      v |= (bytes[0] & 0x7F);
      for (int i = 1; i < 8; i++)
      {
        v <<= 8;
        v |= (bytes[i] & 0xFF);
      }
      return v;
    }
    else
    {
      return Long.MAX_VALUE;
    }
  }
  /**
   * Decode an array of entry ID values from its database representation.
   *
   * @param bytes The raw database value, null if there is no value and
   *              hence no entry IDs.  Zero length means the index entry
   *              limit has been exceeded.
   *              hence no entry IDs. Note that this method will throw an
   *              ArrayIndexOutOfBoundsException if the bytes array length is
   *              not a multiple of 8.
   *
   * @return An array of entry ID values.
   */
  public static long[] entryIDListFromDatabase(byte[] bytes)
  {
    if (bytes == null)
    {
      return new long[0];
    }
    if (bytes.length == 0)
    {
      return null;
    }
    byte[] decodedBytes = bytes;
    int count = decodedBytes.length / 8;
@@ -360,21 +381,32 @@
  }
  /**
   * Encode an entry ID set count to its database representation.
   * @param count The entry ID set count to be encoded.
   * @return The encoded database value of the entry ID.
   */
  public static byte[] entryIDUndefinedSizeToDatabase(long count)
  {
    byte[] bytes = new byte[8];
    long v = count;
    for (int i = 7; i >= 1; i--)
    {
      bytes[i] = (byte) (v & 0xFF);
      v >>>= 8;
    }
    bytes[0] = (byte) ((v | 0x80) & 0xFF);
    return bytes;
  }
  /**
   * Encode an array of entry ID values to its database representation.
   *
   * @param entryIDArray An array of entry ID values. A null value indicates
   * that the entry limit is exceeded, and a zero length array indicates no
   * values.
   * @param entryIDArray An array of entry ID values.
   *
   * @return The encoded database value.
   */
  public static byte[] entryIDListToDatabase(long[] entryIDArray)
  {
    if (entryIDArray == null)
    {
      // index entry limit exceeded
      return new byte[0];
    }
    if (entryIDArray.length == 0)
    {
      // Zero values
opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -49,22 +49,7 @@
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.CancelledOperationException;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.*;
import org.opends.server.util.Validator;
import static org.opends.server.config.ConfigConstants.*;
@@ -387,7 +372,71 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    if (entryDN == null)
    {
      return -1;
    }
    if (entryDN.equals(taskRootDN))
    {
      // scheduled and recurring parents.
      return 2;
    }
    else if (entryDN.equals(scheduledTaskParentDN))
    {
      return taskScheduler.getScheduledTaskCount();
    }
    else if (entryDN.equals(recurringTaskParentDN))
    {
      return taskScheduler.getRecurringTaskCount();
    }
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null)
    {
      return -1;
    }
    if (parentDN.equals(scheduledTaskParentDN) &&
        taskScheduler.getScheduledTask(entryDN) != null)
    {
      return 0;
    }
    else if (parentDN.equals(recurringTaskParentDN) &&
        taskScheduler.getRecurringTask(entryDN) != null)
    {
      return 0;
    }
    else
    {
      return -1;
    }
  }
  /**
   * Retrieves the requested entry from this backend.
opends/src/server/org/opends/server/backends/task/TaskScheduler.java
@@ -1323,6 +1323,46 @@
    }
  }
  /**
   * Retrieves the number of scheduled tasks in the task backend.
   *
   * @return  The total number of entries in the task backend.
   */
  public long getScheduledTaskCount()
  {
    schedulerLock.lock();
    try
    {
      return tasks.size();
    }
    finally
    {
      schedulerLock.unlock();
    }
  }
  /**
   * Retrieves the number of recurring tasks in the task backend.
   *
   * @return  The total number of entries in the task backend.
   */
  public long getRecurringTaskCount()
  {
    schedulerLock.lock();
    try
    {
      return recurringTasks.size();
    }
    finally
    {
      schedulerLock.unlock();
    }
  }
  /**
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -76,29 +76,9 @@
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.schema.GeneralizedTimeSyntax;
import org.opends.server.tools.LDIFModify;
import org.opends.server.types.AttributeType;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.BackupInfo;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.CryptoManager;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ExistingFileBehavior;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.LDIFImportResult;
import org.opends.server.types.Modification;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.*;
import org.opends.server.util.DynamicConstants;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
@@ -1131,7 +1111,39 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException
  {
    long ret = numSubordinates(entryDN);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  public long numSubordinates(DN entryDN) throws DirectoryException
  {
    ConfigEntry baseEntry = configEntries.get(entryDN);
    if (baseEntry == null)
    {
      return -1;
    }
    return baseEntry.getChildren().size();
  }
  /**
   * Retrieves the requested entry from this backend.
opends/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProvider.java
New file
@@ -0,0 +1,279 @@
/*
 * 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 org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.api.Backend;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.config.ConfigException;
import org.opends.server.types.*;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.messages.Message;
import static org.opends.messages.ExtensionMessages.*;
import java.util.LinkedHashSet;
import java.util.List;
/**
 * This class implements a virtual attribute provider that is meant to serve the
 * hasSubordinates operational attribute as described in X.501.
 */
public class HasSubordinatesVirtualAttributeProvider
    extends VirtualAttributeProvider<VirtualAttributeCfg>
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new instance of this HasSubordinates virtual attribute provider.
   */
  public HasSubordinatesVirtualAttributeProvider()
  {
    super();
    // All initialization should be performed in the
    // initializeVirtualAttributeProvider method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeVirtualAttributeProvider(
                            VirtualAttributeCfg configuration)
         throws ConfigException, InitializationException
  {
    // No initialization is required.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMultiValued()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashSet<AttributeValue> getValues(Entry entry,
                                                 VirtualAttributeRule rule)
  {
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    Backend backend = DirectoryServer.getBackend(entry.getDN());
    try
    {
      ConditionResult ret = backend.hasSubordinates(entry.getDN());
      if(ret != null && ret != ConditionResult.UNDEFINED)
      {
        AttributeValue value =
            new AttributeValue(ByteStringFactory.create(ret.toString()),
                               ByteStringFactory.create(ret.toString()));
        values.add(value);
      }
    }
    catch(DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
    }
    return values;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
  {
    Backend backend = DirectoryServer.getBackend(entry.getDN());
    try
    {
      ConditionResult ret = backend.hasSubordinates(entry.getDN());
       return ret != null && ret != ConditionResult.UNDEFINED;
    }
    catch(DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule,
                          AttributeValue value)
  {
     Backend backend = DirectoryServer.getBackend(entry.getDN());
    try
    {
      ConditionResult ret = backend.hasSubordinates(entry.getDN());
      if(ret != null && ret != ConditionResult.UNDEFINED)
      {
        return ConditionResult.valueOf(value.getNormalizedStringValue()).
            equals(ret);
      }
      return false;
    }
    catch(DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult matchesSubstring(Entry entry,
                                          VirtualAttributeRule rule,
                                          ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    // This virtual attribute does not support substring matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult greaterThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // This virtual attribute does not support ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult lessThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // This virtual attribute does not support ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult approximatelyEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // This virtual attribute does not support approximate matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}.  This virtual attribute will support search operations only
   * if one of the following is true about the search filter:
   * <UL>
   *   <LI>It is an equality filter targeting the associated attribute
   *       type.</LI>
   *   <LI>It is an AND filter in which at least one of the components is an
   *       equality filter targeting the associated attribute type.</LI>
   *   <LI>It is an OR filter in which all of the components are equality
   *       filters targeting the associated attribute type.</LI>
   * </UL>
   */
  @Override()
  public boolean isSearchable(VirtualAttributeRule rule,
                              SearchOperation searchOperation)
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSearch(VirtualAttributeRule rule,
                            SearchOperation searchOperation)
  {
    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
    Message message = ERR_HASSUBORDINATES_VATTR_NOT_SEARCHABLE.get(
            rule.getAttributeType().getNameOrOID());
    searchOperation.appendErrorMessage(message);
  }
}
opends/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java
New file
@@ -0,0 +1,250 @@
/*
 * 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 org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.api.Backend;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.config.ConfigException;
import org.opends.server.types.*;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.messages.Message;
import static org.opends.messages.ExtensionMessages.*;
import java.util.LinkedHashSet;
import java.util.List;
/**
 * This class implements a virtual attribute provider that is meant to serve the
 * hasSubordinates operational attribute as described in
 * draft-ietf-boreham-numsubordinates.
 */
public class NumSubordinatesVirtualAttributeProvider
    extends VirtualAttributeProvider<VirtualAttributeCfg>
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new instance of this NumSubordinates virtual attribute provider.
   */
  public NumSubordinatesVirtualAttributeProvider()
  {
    super();
    // All initialization should be performed in the
    // initializeVirtualAttributeProvider method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeVirtualAttributeProvider(
                            VirtualAttributeCfg configuration)
         throws ConfigException, InitializationException
  {
    // No initialization is required.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMultiValued()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashSet<AttributeValue> getValues(Entry entry,
                                                 VirtualAttributeRule rule)
  {
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    Backend backend = DirectoryServer.getBackend(entry.getDN());
    try
    {
      long count = backend.numSubordinates(entry.getDN());
      if(count >= 0)
      {
        AttributeValue value =
            new AttributeValue(ByteStringFactory.create(String.valueOf(count)),
                               ByteStringFactory.create(String.valueOf(count)));
        values.add(value);
      }
    }
    catch(DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
    }
    return values;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
  {
    Backend backend = DirectoryServer.getBackend(entry.getDN());
    try
    {
       return backend.numSubordinates(entry.getDN()) >= 0;
    }
    catch(DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule,
                          AttributeValue value)
  {
     Backend backend = DirectoryServer.getBackend(entry.getDN());
    try
    {
      long count = backend.numSubordinates(entry.getDN());
      if(count >= 0)
      {
        return Long.parseLong(value.getNormalizedStringValue()) == count;
      }
      return false;
    }
    catch(DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult matchesSubstring(Entry entry,
                                          VirtualAttributeRule rule,
                                          ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    // This virtual attribute does not support substring matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult approximatelyEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // This virtual attribute does not support approximate matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}.  This virtual attribute will support search operations only
   * if one of the following is true about the search filter:
   * <UL>
   *   <LI>It is an equality filter targeting the associated attribute
   *       type.</LI>
   *   <LI>It is an AND filter in which at least one of the components is an
   *       equality filter targeting the associated attribute type.</LI>
   *   <LI>It is an OR filter in which all of the components are equality
   *       filters targeting the associated attribute type.</LI>
   * </UL>
   */
  @Override()
  public boolean isSearchable(VirtualAttributeRule rule,
                              SearchOperation searchOperation)
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSearch(VirtualAttributeRule rule,
                            SearchOperation searchOperation)
  {
    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
    Message message = ERR_NUMSUBORDINATES_VATTR_NOT_SEARCHABLE.get(
            rule.getAttributeType().getNameOrOID());
    searchOperation.appendErrorMessage(message);
  }
}
opends/src/server/org/opends/server/tools/DBTest.java
New file
@@ -0,0 +1,1602 @@
/*
 * 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.tools;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.util.StaticUtils.*;
import org.opends.server.util.args.*;
import static org.opends.server.util.ServerConstants.MAX_LINE_WIDTH;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.table.TableBuilder;
import org.opends.server.util.table.TextTablePrinter;
import org.opends.server.types.*;
import static org.opends.messages.ToolMessages.*;
import org.opends.messages.Message;
import static org.opends.server.tools.ToolConstants.OPTION_SHORT_CONFIG_CLASS;
import static org.opends.server.tools.ToolConstants.OPTION_LONG_CONFIG_CLASS;
import static org.opends.server.tools.ToolConstants.OPTION_VALUE_CONFIG_CLASS;
import static org.opends.server.tools.ToolConstants.OPTION_SHORT_HELP;
import static org.opends.server.tools.ToolConstants.OPTION_LONG_HELP;
import org.opends.server.extensions.ConfigFileHandler;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.LockFileManager;
import org.opends.server.config.ConfigException;
import org.opends.server.api.Backend;
import org.opends.server.admin.std.server.BackendCfg;
import org.opends.server.admin.std.server.JEBackendCfg;
import org.opends.server.backends.jeb.*;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Element;
import java.io.*;
import java.util.*;
import com.sleepycat.je.*;
/**
 * This program provides a utility that may be used to debug a JE backend. This
 * tool provides the ability list various containers in the backend as well as
 * dump the contents of database containers. This will be
 * a process that is intended to run separate from Directory Server and not
 * internally within the server process (e.g., via the tasks interface).
 */
public class DBTest
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The error stream which this application should use.
  private final PrintStream err;
  // The output stream which this application should use.
  private final PrintStream out;
  // Flag indicating whether or not the global arguments have
  // already been initialized.
  private boolean globalArgumentsInitialized = false;
  // The command-line argument parser.
  private final SubCommandArgumentParser parser;
  // The argument which should be used to request usage information.
  private BooleanArgument showUsageArgument;
  // The argument which should be used to specify the config class.
  private StringArgument configClass;
  // THe argument which should be used to specify the config file.
  private StringArgument configFile;
  // Flag indicating whether or not the sub-commands have
  // already been initialized.
  private boolean subCommandsInitialized = false;
  /**
   * Provides the command-line arguments to the main application for
   * processing.
   *
   * @param args
   *          The set of command-line arguments provided to this
   *          program.
   */
  public static void main(String[] args) {
    int exitCode = main(args, true, System.out, System.err);
    if (exitCode != 0) {
      System.exit(filterExitCode(exitCode));
    }
  }
  /**
   * Provides the command-line arguments to the main application for
   * processing and returns the exit code as an integer.
   *
   * @param args
   *          The set of command-line arguments provided to this
   *          program.
   * @param initializeServer
   *          Indicates whether to perform basic initialization (which
   *          should not be done if the tool is running in the same
   *          JVM as the server).
   * @param outStream
   *          The output stream for standard output.
   * @param errStream
   *          The output stream for standard error.
   * @return Zero to indicate that the program completed successfully,
   *         or non-zero to indicate that an error occurred.
   */
  public static int main(String[] args, boolean initializeServer,
                         OutputStream outStream, OutputStream errStream) {
    DBTest app = new DBTest(outStream, errStream);
    // Run the application.
    return app.run(args, initializeServer);
  }
  /**
   * Creates a new dsconfig application instance.
   *
   * @param out
   *          The application output stream.
   * @param err
   *          The application error stream.
   */
  public DBTest(OutputStream out, OutputStream err)
  {
    if (out != null) {
      this.out = new PrintStream(out);
    } else {
      this.out = NullOutputStream.printStream();
    }
    if (err != null) {
      this.err = new PrintStream(err);
    } else {
      this.err = NullOutputStream.printStream();
    }
    Message toolDescription = INFO_DESCRIPTION_DBTEST_TOOL.get();
    this.parser = new SubCommandArgumentParser(this.getClass().getName(),
                                               toolDescription, false);
  }
  // Displays the provided message followed by a help usage reference.
  private void displayMessageAndUsageReference(Message message) {
    printMessage(message);
    printMessage(Message.EMPTY);
    printMessage(parser.getHelpUsageReference());
  }
  /**
   * Registers the global arguments with the argument parser.
   *
   * @throws ArgumentException
   *           If a global argument could not be registered.
   */
  private void initializeGlobalArguments() throws ArgumentException {
    if (!globalArgumentsInitialized) {
      configClass =
          new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
                             OPTION_LONG_CONFIG_CLASS, true, false,
                             true, OPTION_VALUE_CONFIG_CLASS,
                             ConfigFileHandler.class.getName(), null,
                             INFO_DESCRIPTION_CONFIG_CLASS.get());
      configClass.setHidden(true);
      configFile =
          new StringArgument("configfile", 'f', "configFile", true, false,
                             true, "{configFile}", null, null,
                             INFO_DESCRIPTION_CONFIG_FILE.get());
      configFile.setHidden(true);
      showUsageArgument =
          new BooleanArgument("help", OPTION_SHORT_HELP, OPTION_LONG_HELP,
                              INFO_DESCRIPTION_USAGE.get());
      // Register the global arguments.
      parser.addGlobalArgument(showUsageArgument);
      parser.setUsageArgument(showUsageArgument, out);
      parser.addGlobalArgument(configClass);
      parser.addGlobalArgument(configFile);
      globalArgumentsInitialized = true;
    }
  }
  /**
   * Registers the sub-commands with the argument parser.
   *
   * @throws ArgumentException
   *           If a sub-command could not be created.
   */
  private void initializeSubCommands() throws ArgumentException {
    if (!subCommandsInitialized) {
      StringArgument backendID;
      StringArgument baseDN;
      StringArgument databaseName;
      BooleanArgument skipDecode;
      StringArgument maxKeyValue;
      StringArgument minKeyValue;
      IntegerArgument maxDataSize;
      IntegerArgument minDataSize;
      SubCommand sub;
      sub = new SubCommand(parser, "list-root-containers",
                     INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_ROOT_CONTAINERS.get());
      sub = new SubCommand(parser, "list-entry-containers",
                    INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_ENTRY_CONTAINERS.get());
      backendID =
          new StringArgument("backendid", 'n', "backendID", true, false, true,
                             "{backendID}", null, null,
                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
      sub.addArgument(backendID);
      sub = new SubCommand(parser, "list-database-containers",
                 INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_DATABASE_CONTAINERS.get());
      backendID =
          new StringArgument("backendid", 'n', "backendID", true, false, true,
                             "{backendID}", null, null,
                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
      sub.addArgument(backendID);
      baseDN =
          new StringArgument("basedn", 'b', "baseDN", false,
                             false, true, "{baseDN}", null, null,
                             INFO_DESCRIPTION_DBTEST_BASE_DN.get());
      sub.addArgument(baseDN);
      sub = new SubCommand(parser, "dump-database-container",
                  INFO_DESCRIPTION_DBTEST_SUBCMD_DUMP_DATABASE_CONTAINER.get());
      backendID =
          new StringArgument("backendid", 'n', "backendID", true, false, true,
                             "{backendID}", null, null,
                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
      sub.addArgument(backendID);
      baseDN =
          new StringArgument("basedn", 'b', "baseDN", true,
                             false, true, "{baseDN}", null, null,
                             INFO_DESCRIPTION_DBTEST_BASE_DN.get());
      sub.addArgument(baseDN);
      databaseName =
          new StringArgument("databasename", 'd', "databaseName", true,
                             false, true, "{databaseName}", null, null,
                             INFO_DESCRIPTION_DBTEST_DATABASE_NAME.get());
      sub.addArgument(databaseName);
      skipDecode =
          new BooleanArgument("skipdecode", 'p', "skipDecode",
                              INFO_DESCRIPTION_DBTEST_SKIP_DECODE.get());
      sub.addArgument(skipDecode);
      maxKeyValue = new StringArgument("maxkeyvalue", 'K', "maxKeyValue", false,
                                       false, true, "{maxKeyValue}", null, null,
                                   INFO_DESCRIPTION_DBTEST_MAX_KEY_VALUE.get());
      sub.addArgument(maxKeyValue);
      minKeyValue = new StringArgument("minkeyvalue", 'k', "minKeyValue", false,
                                       false, true, "{minKeyValue}", null, null,
                                   INFO_DESCRIPTION_DBTEST_MIN_KEY_VALUE.get());
      sub.addArgument(minKeyValue);
      maxDataSize = new IntegerArgument("maxdatasize", 'S', "maxDataSize",
                                        false, false, true, "{maxDataSize}", -1,
                                        null,
                                   INFO_DESCRIPTION_DBTEST_MAX_DATA_SIZE.get());
      sub.addArgument(maxDataSize);
      minDataSize = new IntegerArgument("mindatasize", 's', "minDataSize",
                                        false, false, true, "{minDataSize}", -1,
                                        null,
                                   INFO_DESCRIPTION_DBTEST_MIN_DATA_SIZE.get());
      sub.addArgument(minDataSize);
      sub = new SubCommand(parser, "list-index-status",
                        INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_INDEX_STATUS.get());
      backendID =
          new StringArgument("backendid", 'n', "backendID", true, false, true,
                             "{backendID}", null, null,
                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
      sub.addArgument(backendID);
      baseDN =
          new StringArgument("basedn", 'b', "baseDN", true,
                             true, true, "{baseDN}", null, null,
                             INFO_DESCRIPTION_DBTEST_BASE_DN.get());
      sub.addArgument(baseDN);
      subCommandsInitialized = true;
    }
  }
  /**
   * Parses the provided command-line arguments and makes the
   * appropriate changes to the Directory Server configuration.
   *
   * @param args
   *          The command-line arguments provided to this program.
   * @param initializeServer
   *          Indicates whether to perform basic initialization (which
   *          should not be done if the tool is running in the same
   *          JVM as the server).
   * @return The exit code from the configuration processing. A
   *         nonzero value indicates that there was some kind of
   *         problem during the configuration processing.
   */
  private int run(String[] args, boolean initializeServer) {
    // Register global arguments and sub-commands.
    try {
      initializeGlobalArguments();
      initializeSubCommands();
    } catch (ArgumentException e) {
      Message message = ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage());
      printMessage(message);
      return 1;
    }
    // Parse the command-line arguments provided to this program.
    try {
      parser.parseArguments(args);
    } catch (ArgumentException ae) {
      Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
      displayMessageAndUsageReference(message);
      return 1;
    }
    // If the usage/version argument was provided, then we don't need
    // to do anything else.
    if (parser.usageOrVersionDisplayed()) {
      return 0;
    }
    // Only initialize the server when run as a standalone
    // application.
    if (initializeServer) {
      // Perform the initial bootstrap of the Directory Server and process the
      // configuration.
      DirectoryServer directoryServer = DirectoryServer.getInstance();
      try
      {
        directoryServer.bootstrapClient();
        directoryServer.initializeJMX();
      }
      catch (Exception e)
      {
        Message message = ERR_SERVER_BOOTSTRAP_ERROR.get(
                getExceptionMessage(e));
        printMessage(message);
        return 1;
      }
      try
      {
        directoryServer.initializeConfiguration(configClass.getValue(),
                                                configFile.getValue());
      }
      catch (InitializationException ie)
      {
        Message message = ERR_CANNOT_LOAD_CONFIG.get(
            ie.getMessage());
        printMessage(message);
        return 1;
      }
      catch (Exception e)
      {
        Message message = ERR_CANNOT_LOAD_CONFIG.get(
            getExceptionMessage(e));
        printMessage(message);
        return 1;
      }
      // Initialize the Directory Server schema elements.
      try
      {
        directoryServer.initializeSchema();
      }
      catch (ConfigException ce)
      {
        Message message = ERR_CANNOT_LOAD_SCHEMA.get(
            ce.getMessage());
        printMessage(message);
        return 1;
      }
      catch (InitializationException ie)
      {
        Message message = ERR_CANNOT_LOAD_SCHEMA.get(
            ie.getMessage());
        printMessage(message);
        return 1;
      }
      catch (Exception e)
      {
        Message message = ERR_CANNOT_LOAD_SCHEMA.get(
            getExceptionMessage(e));
        printMessage(message);
        return 1;
      }
    }
    // Make sure that we have a sub-command.
    if (parser.getSubCommand() == null)
    {
      Message message = ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get();
      displayMessageAndUsageReference(message);
      return 1;
    }
    // Retrieve the sub-command implementation and run it.
    SubCommand subCommand = parser.getSubCommand();
    try {
      if(subCommand.getName().equals("list-root-containers"))
      {
        return listRootContainers();
      }
      else if(subCommand.getName().equals("list-entry-containers"))
      {
        return listEntryContainers(subCommand.getArgument("backendid"));
      }
      else if(subCommand.getName().equals("list-database-containers"))
      {
        return listDatabaseContainers(subCommand.getArgument("backendid"),
                                      subCommand.getArgument("basedn"));
      }
      else if(subCommand.getName().equals("dump-database-container"))
      {
        return dumpDatabaseContainer(subCommand.getArgument("backendid"),
                                     subCommand.getArgument("basedn"),
                                     subCommand.getArgument("databasename"),
                                     subCommand.getArgument("skipdecode"),
                                     subCommand.getArgument("maxkeyvalue"),
                                     subCommand.getArgument("minkeyvalue"),
                                     subCommand.getArgument("maxdatasize"),
                                     subCommand.getArgument("mindatasize"));
      }
      else if(subCommand.getName().equals("list-index-status"))
      {
        return listIndexStatus(subCommand.getArgument("backendid"),
                                      subCommand.getArgument("basedn"));
      }
      {
        return 0;
      }
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      printMessage(Message.raw(StaticUtils.stackTraceToString(e)));
      return 1;
    }
  }
  private int listRootContainers()
  {
    TreeMap<JEBackendCfg, BackendImpl> jeBackends = getJEBackends();
    int count = 0;
    // Create a table of their properties.
    TableBuilder builder = new TableBuilder();
    builder.appendHeading(INFO_LABEL_DBTEST_BACKEND_ID.get());
    builder.appendHeading(INFO_LABEL_DBTEST_DB_DIRECTORY.get());
    for(Map.Entry<JEBackendCfg, BackendImpl> backend : jeBackends.entrySet())
    {
      builder.startRow();
      builder.appendCell(backend.getValue().getBackendID());
      builder.appendCell(backend.getKey().getBackendDirectory());
      count++;
    }
    TextTablePrinter printer = new TextTablePrinter(out);
    builder.print(printer);
    out.format("%nTotal: %d%n", count);
    return 0;
  }
  private int listEntryContainers(Argument backendID)
  {
    TreeMap<JEBackendCfg, BackendImpl> jeBackends = getJEBackends();
    BackendImpl backend = null;
    for(BackendImpl b : jeBackends.values())
    {
      if(b.getBackendID().equalsIgnoreCase(backendID.getValue()))
      {
        backend = b;
        break;
      }
    }
    if(backend == null)
    {
      printMessage(ERR_DBTEST_NO_BACKENDS_FOR_ID.get(backendID.getValue()));
      return 1;
    }
    // Acquire an shared lock for the backend.
    try
    {
      String lockFile = LockFileManager.getBackendLockFileName(backend);
      StringBuilder failureReason = new StringBuilder();
      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
      {
        Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
            backend.getBackendID(), String.valueOf(failureReason));
        printMessage(message);
        return 1;
      }
    }
    catch (Exception e)
    {
      Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
          backend.getBackendID(), getExceptionMessage(e));
      printMessage(message);
      return 1;
    }
    RootContainer rc;
    try
    {
      rc = backend.getReadOnlyRootContainer();
    }
    catch(Exception e)
    {
      printMessage(ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(
          backend.getBackendID(),
          StaticUtils.stackTraceToSingleLineString(e)));
      return 1;
    }
    try
    {
      // Create a table of their properties.
      TableBuilder builder = new TableBuilder();
      int count = 0;
      builder.appendHeading(INFO_LABEL_DBTEST_BASE_DN.get());
      builder.appendHeading(INFO_LABEL_DBTEST_JE_DATABASE_PREFIX.get());
      builder.appendHeading(INFO_LABEL_DBTEST_ENTRY_COUNT.get());
      for(EntryContainer ec : rc.getEntryContainers())
      {
        builder.startRow();
        builder.appendCell(ec.getBaseDN().toNormalizedString());
        builder.appendCell(ec.getDatabasePrefix());
        builder.appendCell(ec.getEntryCount());
        count++;
      }
      TextTablePrinter printer = new TextTablePrinter(out);
      builder.print(printer);
      out.format("%nTotal: %d%n", count);
      return 0;
    }
    catch(DatabaseException de)
    {
      printMessage(ERR_DBTEST_ERROR_READING_DATABASE.get(
          StaticUtils.stackTraceToSingleLineString(de)));
      return 1;
    }
    finally
    {
      try
      {
        // Close the root container
        rc.close();
      }
      catch(DatabaseException de)
      {
        // Ignore.
      }
      // Release the shared lock on the backend.
      try
      {
        String lockFile = LockFileManager.getBackendLockFileName(backend);
        StringBuilder failureReason = new StringBuilder();
        if (! LockFileManager.releaseLock(lockFile, failureReason))
        {
        Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
            backend.getBackendID(), String.valueOf(failureReason));
          printMessage(message);
        }
      }
      catch (Exception e)
      {
      Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
          backend.getBackendID(), getExceptionMessage(e));
        printMessage(message);
      }
    }
  }
  private int listDatabaseContainers(Argument backendID,
                                     Argument baseDN)
  {
    TreeMap<JEBackendCfg, BackendImpl> jeBackends = getJEBackends();
    BackendImpl backend = null;
    DN base = null;
    for(BackendImpl b : jeBackends.values())
    {
      if(b.getBackendID().equalsIgnoreCase(backendID.getValue()))
      {
        backend = b;
        break;
      }
    }
    if(backend == null)
    {
      printMessage(ERR_DBTEST_NO_BACKENDS_FOR_ID.get(backendID.getValue()));
      return 1;
    }
    if(baseDN.isPresent())
    {
      try
      {
        base = DN.decode(baseDN.getValue());
      }
      catch(DirectoryException de)
      {
        printMessage(ERR_DBTEST_DECODE_BASE_DN.get(backendID.getValue(),
                                                   getExceptionMessage(de)));
        return 1;
      }
    }
    // Acquire an shared lock for the backend.
    try
    {
      String lockFile = LockFileManager.getBackendLockFileName(backend);
      StringBuilder failureReason = new StringBuilder();
      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
      {
        Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
            backend.getBackendID(), String.valueOf(failureReason));
        printMessage(message);
        return 1;
      }
    }
    catch (Exception e)
    {
      Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
          backend.getBackendID(), getExceptionMessage(e));
      printMessage(message);
      return 1;
    }
    RootContainer rc;
    try
    {
      rc = backend.getReadOnlyRootContainer();
    }
    catch(Exception e)
    {
      printMessage(ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(
          backend.getBackendID(),
          StaticUtils.stackTraceToSingleLineString(e)));
      return 1;
    }
    try
    {
      // Create a table of their properties.
      TableBuilder builder = new TableBuilder();
      int count = 0;
      builder.appendHeading(INFO_LABEL_DBTEST_DATABASE_NAME.get());
      builder.appendHeading(INFO_LABEL_DBTEST_DATABASE_TYPE.get());
      builder.appendHeading(INFO_LABEL_DBTEST_JE_DATABASE_NAME.get());
      builder.appendHeading(INFO_LABEL_DBTEST_ENTRY_COUNT.get());
      if(base != null)
      {
        EntryContainer ec = rc.getEntryContainer(base);
        if(ec == null)
        {
          printMessage(ERR_DBTEST_NO_ENTRY_CONTAINERS_FOR_BASE_DN.get(
              base.toNormalizedString(), backend.getBackendID()));
          return 1;
        }
        ArrayList<DatabaseContainer> databaseContainers =
            new ArrayList<DatabaseContainer>();
        ec.listDatabases(databaseContainers);
        for(DatabaseContainer dc : databaseContainers)
        {
          builder.startRow();
          builder.appendCell(dc.getName().replace(ec.getDatabasePrefix()+"_",
                                                  ""));
          builder.appendCell(dc.getClass().getSimpleName());
          builder.appendCell(dc.getName());
          builder.appendCell(dc.getRecordCount());
          count++;
        }
      }
      else
      {
        for(EntryContainer ec : rc.getEntryContainers())
        {
          builder.startRow();
          ArrayList<DatabaseContainer> databaseContainers =
              new ArrayList<DatabaseContainer>();
          ec.listDatabases(databaseContainers);
          builder.appendCell("Base DN: " +
              ec.getBaseDN().toNormalizedString());
          for(DatabaseContainer dc : databaseContainers)
          {
            builder.startRow();
            builder.appendCell(dc.getName().replace(
                ec.getDatabasePrefix()+"_",""));
            builder.appendCell(dc.getClass().getSimpleName());
            builder.appendCell(dc.getName());
            builder.appendCell(dc.getRecordCount());
            count++;
          }
        }
      }
      TextTablePrinter printer = new TextTablePrinter(out);
      builder.print(printer);
      out.format("%nTotal: %d%n", count);
      return 0;
    }
    catch(DatabaseException de)
    {
      printMessage(ERR_DBTEST_ERROR_READING_DATABASE.get(
          StaticUtils.stackTraceToSingleLineString(de)));
      return 1;
    }
    finally
    {
      try
      {
        // Close the root container
        rc.close();
      }
      catch(DatabaseException de)
      {
        // Ignore.
      }
      // Release the shared lock on the backend.
      try
      {
        String lockFile = LockFileManager.getBackendLockFileName(backend);
        StringBuilder failureReason = new StringBuilder();
        if (! LockFileManager.releaseLock(lockFile, failureReason))
        {
          Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
              backend.getBackendID(), String.valueOf(failureReason));
          printMessage(message);
        }
      }
      catch (Exception e)
      {
        Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
            backend.getBackendID(), getExceptionMessage(e));
        printMessage(message);
      }
    }
  }
  private int listIndexStatus(Argument backendID,
                              Argument baseDN)
  {
    TreeMap<JEBackendCfg, BackendImpl> jeBackends = getJEBackends();
    BackendImpl backend = null;
    DN base = null;
    for(BackendImpl b : jeBackends.values())
    {
      if(b.getBackendID().equalsIgnoreCase(backendID.getValue()))
      {
        backend = b;
        break;
      }
    }
    if(backend == null)
    {
      printMessage(ERR_DBTEST_NO_BACKENDS_FOR_ID.get(backendID.getValue()));
      return 1;
    }
    if(baseDN.isPresent())
    {
      try
      {
        base = DN.decode(baseDN.getValue());
      }
      catch(DirectoryException de)
      {
        printMessage(ERR_DBTEST_DECODE_BASE_DN.get(backendID.getValue(),
                                                   getExceptionMessage(de)));
        return 1;
      }
    }
    // Acquire an shared lock for the backend.
    try
    {
      String lockFile = LockFileManager.getBackendLockFileName(backend);
      StringBuilder failureReason = new StringBuilder();
      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
      {
        Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
            backend.getBackendID(), String.valueOf(failureReason));
        printMessage(message);
        return 1;
      }
    }
    catch (Exception e)
    {
      Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
          backend.getBackendID(), getExceptionMessage(e));
      printMessage(message);
      return 1;
    }
    RootContainer rc;
    try
    {
      rc = backend.getReadOnlyRootContainer();
    }
    catch(Exception e)
    {
      printMessage(ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(
          backend.getBackendID(),
          StaticUtils.stackTraceToSingleLineString(e)));
      return 1;
    }
    try
    {
      // Create a table of their properties.
      TableBuilder builder = new TableBuilder();
      int count = 0;
      builder.appendHeading(INFO_LABEL_DBTEST_INDEX_NAME.get());
      builder.appendHeading(INFO_LABEL_DBTEST_INDEX_TYPE.get());
      builder.appendHeading(INFO_LABEL_DBTEST_JE_DATABASE_NAME.get());
      builder.appendHeading(INFO_LABEL_DBTEST_INDEX_STATUS.get());
      EntryContainer ec = rc.getEntryContainer(base);
      if(ec == null)
      {
        printMessage(ERR_DBTEST_NO_ENTRY_CONTAINERS_FOR_BASE_DN.get(
            base.toNormalizedString(), backend.getBackendID()));
        return 1;
      }
      ArrayList<DatabaseContainer> databaseContainers =
          new ArrayList<DatabaseContainer>();
      ec.listDatabases(databaseContainers);
      for(DatabaseContainer dc : databaseContainers)
      {
        if(dc instanceof Index || dc instanceof VLVIndex)
        {
          builder.startRow();
          builder.appendCell(dc.getName().replace(ec.getDatabasePrefix()+"_",
                                                  ""));
          builder.appendCell(dc.getClass().getSimpleName());
          builder.appendCell(dc.getName());
          if(dc instanceof Index)
          {
            builder.appendCell(ec.getState().getIndexTrustState(null,
                                                                ((Index)dc)));
          }
          else if(dc instanceof VLVIndex)
          {
            builder.appendCell(ec.getState().getIndexTrustState(null,
                                                               ((VLVIndex)dc)));
          }
          count++;
        }
      }
      TextTablePrinter printer = new TextTablePrinter(out);
      builder.print(printer);
      out.format("%nTotal: %d%n", count);
      return 0;
    }
    catch(DatabaseException de)
    {
      printMessage(ERR_DBTEST_ERROR_READING_DATABASE.get(
          StaticUtils.stackTraceToSingleLineString(de)));
      return 1;
    }
    finally
    {
      try
      {
        // Close the root container
        rc.close();
      }
      catch(DatabaseException de)
      {
        // Ignore.
      }
      // Release the shared lock on the backend.
      try
      {
        String lockFile = LockFileManager.getBackendLockFileName(backend);
        StringBuilder failureReason = new StringBuilder();
        if (! LockFileManager.releaseLock(lockFile, failureReason))
        {
        Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
            backend.getBackendID(), String.valueOf(failureReason));
          printMessage(message);
        }
      }
      catch (Exception e)
      {
      Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
          backend.getBackendID(), getExceptionMessage(e));
        printMessage(message);
      }
    }
  }
  private int dumpDatabaseContainer(Argument backendID, Argument baseDN,
                                    Argument databaseName, Argument skipDecode,
                                    Argument maxKeyValue, Argument minKeyValue,
                                    Argument maxDataSize,
                                    Argument minDataSize)
  {
    TreeMap<JEBackendCfg, BackendImpl> jeBackends = getJEBackends();
    BackendImpl backend = null;
    DN base = null;
    for(BackendImpl b : jeBackends.values())
    {
      if(b.getBackendID().equalsIgnoreCase(backendID.getValue()))
      {
        backend = b;
        break;
      }
    }
    if(backend == null)
    {
      printMessage(ERR_DBTEST_NO_BACKENDS_FOR_ID.get(backendID.getValue()));
      return 1;
    }
    try
    {
      base = DN.decode(baseDN.getValue());
    }
    catch(DirectoryException de)
    {
      printMessage(ERR_DBTEST_DECODE_BASE_DN.get(backendID.getValue(),
                                                 getExceptionMessage(de)));
      return 1;
    }
    // Acquire an shared lock for the backend.
    try
    {
      String lockFile = LockFileManager.getBackendLockFileName(backend);
      StringBuilder failureReason = new StringBuilder();
      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
      {
        Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
            backend.getBackendID(), String.valueOf(failureReason));
        printMessage(message);
        return 1;
      }
    }
    catch (Exception e)
    {
      Message message = ERR_DBTEST_CANNOT_LOCK_BACKEND.get(
          backend.getBackendID(), getExceptionMessage(e));
      printMessage(message);
      return 1;
    }
    RootContainer rc;
    try
    {
      rc = backend.getReadOnlyRootContainer();
    }
    catch(Exception e)
    {
      printMessage(ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(
          backend.getBackendID(),
          StaticUtils.stackTraceToSingleLineString(e)));
      return 1;
    }
    try
    {
      EntryContainer ec = rc.getEntryContainer(base);
      if(ec == null)
      {
        printMessage(ERR_DBTEST_NO_ENTRY_CONTAINERS_FOR_BASE_DN.get(
            base.toNormalizedString(), backend.getBackendID()));
        return 1;
      }
      DatabaseContainer databaseContainer = null;
      ArrayList<DatabaseContainer> databaseContainers =
          new ArrayList<DatabaseContainer>();
      ec.listDatabases(databaseContainers);
      for(DatabaseContainer dc : databaseContainers)
      {
        if(dc.getName().replace(ec.getDatabasePrefix()+"_","").
            equalsIgnoreCase(databaseName.getValue()))
        {
          databaseContainer = dc;
          break;
        }
      }
      if(databaseContainer == null)
      {
        printMessage(ERR_DBTEST_NO_DATABASE_CONTAINERS_FOR_NAME.get(
            databaseName.getValue(), base.toNormalizedString(),
            backend.getBackendID()));
        return 1;
      }
      int count = 0;
      long totalKeySize = 0;
      long totalDataSize = 0;
      int indent = 4;
      Cursor cursor =
          databaseContainer.openCursor(null, CursorConfig.DEFAULT);
      try
      {
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
        LockMode lockMode = LockMode.DEFAULT;
        OperationStatus status;
        Comparator<byte[]> defaultComparator =
            new AttributeIndex.KeyComparator();
        Comparator<byte[]> dnComparator =
            new EntryContainer.KeyReverseComparator();
        byte[] start = null;
        byte[] end = null;
        int minSize = -1;
        int maxSize = -1;
        if(maxDataSize.isPresent())
        {
          try
          {
            maxSize = maxDataSize.getIntValue();
          }
          catch(Exception e)
          {
            printMessage(ERR_DBTEST_CANNOT_DECODE_SIZE.get(
                maxDataSize.getValue(), getExceptionMessage(e)));
            return 1;
          }
        }
        if(minDataSize.isPresent())
        {
          try
          {
            minSize = minDataSize.getIntValue();
          }
          catch(Exception e)
          {
            printMessage(ERR_DBTEST_CANNOT_DECODE_SIZE.get(
                minDataSize.getValue(), getExceptionMessage(e)));
            return 1;
          }
        }
        // Parse the min value if given
        if(minKeyValue.isPresent())
        {
          try
          {
            if(minKeyValue.getValue().startsWith("0x"))
            {
              start =
                  StaticUtils.hexStringToByteArray(minKeyValue.getValue().
                      substring(2));
            }
            else
            {
              if(databaseContainer instanceof DN2ID ||
                  databaseContainer instanceof DN2URI)
              {
                // Encode the value as a DN
                start = StaticUtils.getBytes(
                    DN.decode(minKeyValue.getValue()).toNormalizedString());
              }
              else if(databaseContainer instanceof ID2Entry)
              {
                // Encode the value as an entryID
                start = JebFormat.entryIDToDatabase(
                    Long.parseLong(minKeyValue.getValue()));
              }
              else if(databaseContainer instanceof VLVIndex)
              {
                // Encode the value as a size/value pair
                byte[] vBytes =
                    new ASN1OctetString(minKeyValue.getValue()).value();
                byte[] vLength = ASN1Element.encodeLength(vBytes.length);
                start = new byte[vBytes.length + vLength.length];
                System.arraycopy(vLength, 0, start, 0, vLength.length);
                System.arraycopy(vBytes, 0, start, vLength.length,
                                 vBytes.length);
              }
              else
              {
                start = new ASN1OctetString(minKeyValue.getValue()).value();
              }
            }
          }
          catch(Exception e)
          {
            printMessage(ERR_DBTEST_CANNOT_DECODE_KEY.get(
                minKeyValue.getValue(), getExceptionMessage(e)));
            return 1;
          }
        }
        // Parse the max value if given
        if(maxKeyValue.isPresent())
        {
          try
          {
            if(maxKeyValue.getValue().startsWith("0x"))
            {
              end =
                  StaticUtils.hexStringToByteArray(maxKeyValue.getValue().
                      substring(2));
            }
            else
            {
              if(databaseContainer instanceof DN2ID ||
                  databaseContainer instanceof DN2URI)
              {
                // Encode the value as a DN
                end = StaticUtils.getBytes(
                    DN.decode(maxKeyValue.getValue()).toNormalizedString());
              }
              else if(databaseContainer instanceof ID2Entry)
              {
                // Encode the value as an entryID
                end = JebFormat.entryIDToDatabase(
                    Long.parseLong(maxKeyValue.getValue()));
              }
              else if(databaseContainer instanceof VLVIndex)
              {
                // Encode the value as a size/value pair
                byte[] vBytes =
                    new ASN1OctetString(maxKeyValue.getValue()).value();
                byte[] vLength = ASN1Element.encodeLength(vBytes.length);
                end = new byte[vBytes.length + vLength.length];
                System.arraycopy(vLength, 0, end, 0, vLength.length);
                System.arraycopy(vBytes, 0, end, vLength.length,
                                 vBytes.length);
              }
              else
              {
                end = new ASN1OctetString(maxKeyValue.getValue()).value();
              }
            }
          }
          catch(Exception e)
          {
            printMessage(ERR_DBTEST_CANNOT_DECODE_KEY.get(
                maxKeyValue.getValue(), getExceptionMessage(e)));
            return 1;
          }
        }
        if(start != null)
        {
          key.setData(start);
          status = cursor.getSearchKey(key, data, lockMode);
        }
        else
        {
          status = cursor.getFirst(key, data, lockMode);
        }
        while(status == OperationStatus.SUCCESS)
        {
          // Make sure this record is within the value size params
          if((minSize > 0 && data.getSize() < minSize) ||
              (maxSize > 0 && data.getSize() > maxSize))
          {
            status = cursor.getNext(key, data, lockMode);
            continue;
          }
          // Make sure we haven't gone pass the max value yet
          if(end != null)
          {
            if(databaseContainer instanceof DN2ID)
            {
              if(dnComparator.compare(key.getData(), end) > 0)
              {
                break;
              }
            }
            else if(databaseContainer instanceof DN2URI)
            {
              if(dnComparator.compare(key.getData(), end) > 0)
              {
                break;
              }
            }
            else if(databaseContainer instanceof Index)
            {
              if(((Index)databaseContainer).indexer.getComparator().
                  compare(key.getData(), end) > 0)
              {
                break;
              }
            }
            else if(databaseContainer instanceof VLVIndex)
            {
              if(((VLVIndex)databaseContainer).comparator.
                  compare(key.getData(), end) > 0)
              {
                break;
              }
            }
            else
            {
              if(defaultComparator.compare(key.getData(),
                                           end) > 0)
              {
                break;
              }
            }
          }
          Message keyLabel = INFO_LABEL_DBTEST_KEY.get();
          Message dataLabel = INFO_LABEL_DBTEST_DATA.get();
          String formatedKey = null;
          String formatedData = null;
          if(!skipDecode.isPresent())
          {
            if(databaseContainer instanceof DN2ID)
            {
              try
              {
                formatedKey = DN.decode(new ASN1OctetString(
                    key.getData())).toNormalizedString();
                keyLabel = INFO_LABEL_DBTEST_ENTRY_DN.get();
              }
              catch(Exception e)
              {
                Message message =
                    ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e));
                printMessage(message);
              }
              formatedData = String.valueOf(
                  JebFormat.entryIDFromDatabase(data.getData()));
              dataLabel = INFO_LABEL_DBTEST_ENTRY_ID.get();
            }
            else if(databaseContainer instanceof ID2Entry)
            {
              formatedKey = String.valueOf(
                  JebFormat.entryIDFromDatabase(key.getData()));
              keyLabel = INFO_LABEL_DBTEST_ENTRY_ID.get();
              try
              {
                formatedData = System.getProperty("line.separator") +
                    JebFormat.entryFromDatabase(data.getData()).toLDIFString();
                dataLabel = INFO_LABEL_DBTEST_ENTRY.get();
              }
              catch(Exception e)
              {
                Message message =
                    ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e));
                printMessage(message);
              }
            }
            else if(databaseContainer instanceof DN2URI)
            {
              try
              {
                formatedKey = DN.decode(new ASN1OctetString(
                    key.getData())).toNormalizedString();
                keyLabel = INFO_LABEL_DBTEST_ENTRY_DN.get();
              }
              catch(Exception e)
              {
                Message message =
                    ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e));
                printMessage(message);
              }
              formatedData = new ASN1OctetString(key.getData()).stringValue();
              dataLabel = INFO_LABEL_DBTEST_URI.get();
            }
            else if(databaseContainer instanceof Index)
            {
              formatedKey = new ASN1OctetString(key.getData()).stringValue();
              keyLabel = INFO_LABEL_DBTEST_INDEX_VALUE.get();
              EntryIDSet idSet = new EntryIDSet(key.getData(),
                                                data.getData());
              if(idSet.isDefined())
              {
                int lineCount = 0;
                StringBuilder builder = new StringBuilder();
                Iterator<EntryID> i = idSet.iterator();
                while(i.hasNext())
                {
                  builder.append(i.next());
                  if(lineCount == 10)
                  {
                    builder.append(System.getProperty("line.separator"));
                    lineCount = 0;
                  }
                  else
                  {
                    builder.append(" ");
                    lineCount++;
                  }
                }
                formatedData = builder.toString();
              }
              else
              {
                formatedData = idSet.toString();
              }
              dataLabel = INFO_LABEL_DBTEST_INDEX_ENTRY_ID_LIST.get();
            }
            else if(databaseContainer instanceof VLVIndex)
            {
              VLVIndex index = (VLVIndex)databaseContainer;
              SortKey[] sortKeys = index.sortOrder.getSortKeys();
              int pos = 0;
              byte[] keyBytes = key.getData();
              if(keyBytes.length > 0)
              {
                StringBuilder builder = new StringBuilder();
                // Decode the attribute values
                for(SortKey sortKey : sortKeys)
                {
                  int valueLength = keyBytes[pos] & 0x7F;
                  if (keyBytes[pos++] != valueLength)
                  {
                    int numLengthBytes = valueLength;
                    valueLength = 0;
                    for (int k=0; k < numLengthBytes; k++, pos++)
                    {
                      valueLength = (valueLength << 8) |
                          (keyBytes[pos] & 0xFF);
                    }
                  }
                  byte[] valueBytes = new byte[valueLength];
                  System.arraycopy(keyBytes, pos, valueBytes, 0,
                                   valueLength);
                  builder.append(sortKey.getAttributeType().getNameOrOID());
                  builder.append(": ");
                  if(valueBytes.length == 0)
                  {
                    builder.append("NULL");
                  }
                  else
                  {
                    builder.append(
                        new ASN1OctetString(valueBytes).stringValue());
                  }
                  builder.append(" ");
                  pos += valueLength;
                }
                byte[] entryIDBytes = new byte[8];
                System.arraycopy(keyBytes, pos, entryIDBytes, 0,
                                 entryIDBytes.length);
                long entryID = JebFormat.entryIDFromDatabase(entryIDBytes);
                formatedKey = System.getProperty("line.separator") +
                    String.valueOf(entryID) + ": " + builder.toString();
              }
              else
              {
                formatedKey = "UNBOUNDED";
              }
              keyLabel = INFO_LABEL_DBTEST_VLV_INDEX_LAST_SORT_KEYS.get();
              try
              {
                StringBuilder builder = new StringBuilder();
                SortValuesSet svs = new SortValuesSet(key.getData(),
                                                      data.getData(),
                                                      index,
                                                      null);
                long[] entryIDs = svs.getEntryIDs();
                for(int i = 0; i < entryIDs.length; i++)
                {
                  builder.append(String.valueOf(entryIDs[i]));
                  builder.append(": ");
                  for(int j = 0; j < sortKeys.length; j++)
                  {
                    SortKey sortKey = index.sortOrder.getSortKeys()[j];
                    byte[] value = svs.getValue(i * sortKeys.length + j);
                    builder.append(sortKey.getAttributeType().getNameOrOID());
                    builder.append(": ");
                    if(value == null)
                    {
                      builder.append("NULL");
                    }
                    else if(value.length == 0)
                    {
                      builder.append("SIZE-EXCEEDED");
                    }
                    else
                    {
                      builder.append(
                          new ASN1OctetString(value).stringValue());
                    }
                    builder.append(" ");
                  }
                  builder.append(System.getProperty("line.separator"));
                }
                formatedData = System.getProperty("line.separator") +
                    builder.toString();
                dataLabel = INFO_LABEL_DBTEST_INDEX_ENTRY_ID_LIST.get();
              }
              catch(Exception e)
              {
                Message message =
                    ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e));
                printMessage(message);
              }
            }
          }
          if(formatedKey == null)
          {
            StringBuilder keyBuilder = new StringBuilder();
            StaticUtils.byteArrayToHexPlusAscii(keyBuilder, key.getData(),
                                                indent);
            formatedKey = System.getProperty("line.separator") +
                keyBuilder.toString();
          }
          if(formatedData == null)
          {
            StringBuilder dataBuilder = new StringBuilder();
            StaticUtils.byteArrayToHexPlusAscii(dataBuilder, data.getData(),
                                                indent);
            formatedData = System.getProperty("line.separator") +
                dataBuilder.toString();
          }
          out.format("%s (%d bytes): %s%n", keyLabel,
                     key.getData().length, formatedKey);
          out.format("%s (%d bytes): %s%n%n", dataLabel,
                     data.getData().length, formatedData);
          status = cursor.getNext(key, data, lockMode);
          count++;
          totalKeySize += key.getData().length;
          totalDataSize += data.getData().length;
        }
      }
      finally
      {
        cursor.close();
      }
      out.format("%nTotal Records: %d%n", count);
      if(count > 0)
      {
        out.format("Total / Average Key Size: %d bytes / %d bytes%n",
                   totalKeySize, totalKeySize / count);
        out.format("Total / Average Data Size: %d bytes / %d bytes%n",
                   totalDataSize, totalDataSize / count);
      }
      return 0;
    }
    catch(DatabaseException de)
    {
      printMessage(ERR_DBTEST_ERROR_READING_DATABASE.get(
          StaticUtils.stackTraceToSingleLineString(de)));
      return 1;
    }
    finally
    {
      try
      {
        // Close the root container
        rc.close();
      }
      catch(DatabaseException de)
      {
        // Ignore.
      }
      // Release the shared lock on the backend.
      try
      {
        String lockFile = LockFileManager.getBackendLockFileName(backend);
        StringBuilder failureReason = new StringBuilder();
        if (! LockFileManager.releaseLock(lockFile, failureReason))
        {
          Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
              backend.getBackendID(), String.valueOf(failureReason));
          printMessage(message);
        }
      }
      catch (Exception e)
      {
        Message message = WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(
            backend.getBackendID(), getExceptionMessage(e));
        printMessage(message);
      }
    }
  }
  private TreeMap<JEBackendCfg, BackendImpl> getJEBackends()
  {
    ArrayList<Backend> backendList = new ArrayList<Backend>();
    ArrayList<BackendCfg>  entryList   = new ArrayList<BackendCfg>();
    ArrayList<List<DN>> dnList = new ArrayList<List<DN>>();
    int code = BackendToolUtils.getBackends(backendList, entryList, dnList);
    // TODO: Throw error if return code is not 0
    TreeMap<JEBackendCfg, BackendImpl> jeBackends =
        new TreeMap<JEBackendCfg, BackendImpl>();
    for(int i = 0; i < backendList.size(); i++)
    {
      Backend backend = backendList.get(i);
      if(backend instanceof BackendImpl)
      {
        jeBackends.put((JEBackendCfg)entryList.get(i), (BackendImpl)backend);
      }
    }
    return jeBackends;
  }
  /**
   * Displays a message to the error stream.
   *
   * @param msg
   *          The message.
   */
  public final void printMessage(Message msg) {
    err.println(wrapText(msg.toString(), MAX_LINE_WIDTH));
  }
}
opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -754,20 +754,6 @@
ds-cfg-index-attribute: uid
ds-cfg-index-type: equality
dn: ds-cfg-index-attribute=ds-sync-hist,cn=Index,ds-cfg-backend-id=indexRoot,cn=Backends,cn=config
changetype: add
objectClass: top
objectClass: ds-cfg-je-index
ds-cfg-index-attribute: ds-sync-hist
ds-cfg-index-type: ordering
dn: ds-cfg-index-attribute=entryuuid,cn=Index,ds-cfg-backend-id=indexRoot,cn=Backends,cn=config
changetype: add
objectClass: top
objectClass: ds-cfg-je-index
ds-cfg-index-attribute: entryuuid
ds-cfg-index-type: equality
dn: cn=Virtual Static member,cn=Virtual Attributes,cn=config
changetype: modify
replace: ds-cfg-allow-retrieving-membership
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -586,6 +586,23 @@
  }
  @Test(dependsOnMethods = "testAdd")
  public void testNumSubordinates() throws Exception
  {
    DN dn = DN.decode("dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), 1);
    dn = DN.decode("ou=People,dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), 12);
    dn = DN.decode("dc=com");
    assertEquals(backend.numSubordinates(dn), -1);
    dn = DN.decode("dc=test1,dc=com");
    assertEquals(backend.numSubordinates(dn), 2);
    dn = DN.decode("uid=user.10,ou=People,dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), 0);
    dn = DN.decode("uid=does not exist,ou=People,dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), -1);
  }
  @Test(dependsOnMethods = "testAdd")
  public void testSearchIndex() throws Exception {
    InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
@@ -719,7 +736,8 @@
  @Test(dependsOnMethods = {"testAdd", "testSearchIndex",
      "testSearchScope", "testSearchNotIndexed", "testModifyDNNewSuperior",
      "testMatchedDN"})
      "testMatchedDN", "testNumSubordinates",
      "testNumSubordinatesIndexEntryLimitExceeded"})
  public void testDeleteSubtree() throws Exception {
    Control control = new Control(OID_SUBTREE_DELETE_CONTROL, false);
    ArrayList<Control> deleteSubTreeControl = new ArrayList<Control>();
@@ -852,7 +870,8 @@
  }
  @Test(dependsOnMethods = {"testSearchNotIndexed", "testAdd",
      "testSearchIndex", "testSearchScope", "testMatchedDN"})
      "testSearchIndex", "testSearchScope", "testMatchedDN",
      "testNumSubordinates", "testNumSubordinatesIndexEntryLimitExceeded"})
  public void testReplaceEntry() throws Exception {
    Entry entry;
    Entry oldEntry;
@@ -971,7 +990,8 @@
  }
  @Test(dependsOnMethods = {"testSearchNotIndexed", "testAdd",
      "testSearchIndex", "testSearchScope", "testMatchedDN"})
      "testSearchIndex", "testSearchScope", "testMatchedDN",
      "testNumSubordinates", "testNumSubordinatesIndexEntryLimitExceeded"})
  public void testModifyEntry() throws Exception {
    Entry entry;
    Entry newEntry;
@@ -1145,7 +1165,8 @@
  @Test(dependsOnMethods = {"testSearchNotIndexed", "testAdd", "testSearchIndex",
      "testSearchScope", "testModifyEntry", "testModifyDN", "testReplaceEntry",
      "testDeleteEntry", "testMatchedDN"})
      "testDeleteEntry", "testMatchedDN", "testNumSubordinates",
      "testNumSubordinatesIndexEntryLimitExceeded"})
  public void testModifyDNNewSuperior() throws Exception {
    //Add the new superior entry we want to move to. Test to see if the child ID
    //always above parent invarient is preseved.
@@ -1502,6 +1523,25 @@
  }
    @Test(dependsOnMethods = "testSearchNotIndexed")
  public void testNumSubordinatesIndexEntryLimitExceeded() throws Exception
  {
    DN dn = DN.decode("dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), 1);
    // 1 entry was deleted and 2 added for a total of 13
    dn = DN.decode("ou=People,dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), 13);
    dn = DN.decode("dc=com");
    assertEquals(backend.numSubordinates(dn), -1);
    dn = DN.decode("dc=test1,dc=com");
    assertEquals(backend.numSubordinates(dn), 2);
    dn = DN.decode("uid=user.10,ou=People,dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), 0);
    dn = DN.decode("uid=does not exist,ou=People,dc=test,dc=com");
    assertEquals(backend.numSubordinates(dn), -1);
  }
  /**
   * Provides a set of DNs for the matched DN test case.
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProviderTestCase.java
New file
@@ -0,0 +1,581 @@
/*
 * 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 org.opends.server.types.*;
import org.opends.server.TestCaseUtils;
import static org.opends.server.util.StaticUtils.getBytes;
import static org.opends.server.util.ServerConstants.OID_REAL_ATTRS_ONLY;
import static org.opends.server.util.ServerConstants.OID_VIRTUAL_ATTRS_ONLY;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.core.DirectoryServer;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertEquals;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.UUID;
import java.util.LinkedList;
public class HasSubordinatesVirtualAttributeProviderTestCase
{
      // The attribute type for the hasSubordinates attribute.
  private AttributeType hasSubordinatesType;
  private List<Entry> entries;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    hasSubordinatesType =
        DirectoryServer.getAttributeType("hassubordinates", false);
    assertNotNull(hasSubordinatesType);
    entries = TestCaseUtils.makeEntries(
        "dn: dc=example,dc=com",
        "objectclass: top",
        "objectclass: domain",
        "dc: example",
        "",
        "dn: ou=People,dc=example,dc=com",
        "objectclass: top",
        "objectclass: organizationalUnit",
        "ou: People",
        "",
        "dn: ou=Employees,ou=People,dc=example,dc=com",
        "objectclass: top",
        "objectclass: organizationalUnit",
        "ou: Employees",
        "",
        "dn: ou=Buildings,dc=example,dc=com",
        "objectclass: top",
        "objectclass: organizationalUnit",
        "ou: Buildings",
        "",
        "dn: uid=user.0,ou=People,dc=example,dc=com",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "givenName: Aaccf",
        "sn: Amar",
        "cn: Aaccf Amar",
        "initials: AQA",
        "employeeNumber: 0",
        "uid: user.0",
        "mail: user.0@example.com",
        "userPassword: password",
        "telephoneNumber: 380-535-2354",
        "homePhone: 707-626-3913",
        "pager: 456-345-7750",
        "mobile: 366-674-7274",
        "street: 99262 Eleventh Street",
        "l: Salem",
        "st: NM",
        "postalCode: 36530",
        "postalAddress: Aaccf Amar$99262 Eleventh Street$Salem, NM  36530",
        "description: This is the description for Aaccf Amar.",
        "",
        "dn: uid=user.1,ou=People,dc=example,dc=com",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "givenName: Aaren",
        "sn: Atp",
        "cn: Aaren Atp",
        "initials: APA",
        "employeeNumber: 1",
        "uid: user.1",
        "mail: user.1@example.com",
        "userPassword: password",
        "telephoneNumber: 643-278-6134",
        "homePhone: 546-786-4099",
        "pager: 508-261-3187",
        "mobile: 377-267-7824",
        "street: 78113 Fifth Street",
        "l: Chico",
        "st: TN",
        "postalCode: 72322",
        "postalAddress: Aaren Atp$78113 Fifth Street$Chico, TN  72322",
        "description: This is the description for Aaren Atp.",
        "",
        "dn: uid=user.2,ou=Employees,ou=People,dc=example,dc=com",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "givenName: Aarika",
        "sn: Atpco",
        "cn: Aarika Atpco",
        "initials: ARA",
        "employeeNumber: 2",
        "uid: user.2",
        "mail: user.2@example.com",
        "userPassword: password",
        "telephoneNumber: 547-504-3498",
        "homePhone: 955-899-7308",
        "pager: 710-832-9316",
        "mobile: 688-388-4525",
        "street: 59208 Elm Street",
        "l: Youngstown",
        "st: HI",
        "postalCode: 57377",
        "postalAddress: Aarika Atpco$59208 Elm Street$Youngstown, HI  57377",
        "description: This is the description for Aarika Atpco.");
    TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(entries);
  }
    /**
   * Retrieves a set of entry DNs for use in testing the hasSubordinates virtual
   * attribute.
   *
   * @return  A set of entry DNs for use in testing the hasSubordinates virtual
   *          attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "testEntryDNs")
  public Object[][] getTestEntryDNs()
         throws Exception
  {
    return new Object[][]
    {
      new Object[] { DN.decode("dc=example,dc=com"), true },
      new Object[] { DN.decode("ou=People,dc=example,dc=com"), true },
      new Object[] { DN.decode("ou=Employees,ou=People,dc=example,dc=com"), true },
      new Object[] { DN.decode("ou=Buildings,dc=example,dc=com"), false },
      new Object[] { DN.decode("uid=user.0,ou=People,dc=example,dc=com"), false },
      new Object[] { DN.decode("uid=user.1,ou=People,dc=example,dc=com"), false },
      new Object[] { DN.decode("uid=user.2,ou=Employees,ou=People" +
            ",dc=example,dc=com"), false },
      new Object[] { DN.decode("cn=monitor"), true },
      new Object[] { DN.decode("cn=Backends,cn=config"), true },
      new Object[] { DN.decode("cn=Work Queue,cn=config"), false },
      new Object[] { DN.decode("cn=tasks"), true },
      new Object[] { DN.decode("cn=Recurring Tasks,cn=tasks"), false },
      new Object[] { DN.decode("cn=Scheduled Tasks,cn=tasks"), false },
      new Object[] { DN.decode("cn=backups"), false }
    };
  }
  /**
   * Tests the {@code getEntry} method for the specified entry to ensure that
   * the entry returned includes the hasSubordinates operational attribute
   * with the correct value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   * @param hasSubs Whether this entry has any subs.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testGetEntry(DN entryDN, boolean hasSubs)
      throws Exception
  {
    Entry e = DirectoryServer.getEntry(entryDN);
    assertNotNull(e);
    assertTrue(e.hasAttribute(hasSubordinatesType));
    List<Attribute> attrList = e.getAttribute(hasSubordinatesType);
    assertNotNull(attrList);
    assertFalse(attrList.isEmpty());
    for (Attribute a : attrList)
    {
      assertTrue(a.hasValue());
      assertEquals(a.getValues().size(), 1);
      assertTrue(a.getValues().contains(new AttributeValue(
          ByteStringFactory.create(String.valueOf(hasSubs)),
          ByteStringFactory.create(String.valueOf(hasSubs)))));
      assertTrue(a.hasValue(new AttributeValue(hasSubordinatesType,
                                               String.valueOf(hasSubs))));
    }
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is not included when the list of attributes
   * requested is empty (defaulting to all user attributes).
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEmptyAttrs(DN entryDN, boolean hasSubs)
      throws Exception
  {
    SearchFilter filter =
        SearchFilter.createFilterFromString("(objectClass=*)");
    InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
        conn.processSearch(entryDN, SearchScope.BASE_OBJECT, filter);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is not included when the list of requested
   * attributes is "1.1", meaning no attributes.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchNoAttrs(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("1.1");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is not included when all user attributes are
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllUserAttrs(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("*");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is included when all operational attributes are
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllOperationalAttrs(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("+");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is included when that attribute is specifically
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchhasSubordinatesAttr(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("hasSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is not included when it is not in the list of
   * attributes that is explicitly requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchExcludehasSubordinatesAttr(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("objectClass");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is included when that attribute is specifically
   * requested and the hasSubordinates attribute is used in the search filter with a
   * matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchhasSubordinatesAttrInMatchingFilter(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(hasSubordinates=" + hasSubs + ")");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("hasSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * no entries are returned when the hasSubordinates attribute is used in the search
   * filter with a non-matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchhasSubordinatesAttrInNonMatchingFilter(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(hasSubordinates=wrong)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("hasSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 0);
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is not included when that attribute is specifically
   * requested and the real attributes only control is included in the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchhasSubordinatesAttrRealAttrsOnly(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("hasSubordinates");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_REAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is included when that attribute is specifically
   * requested and the virtual attributes only control is included
   * in the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchhasSubordinatesAttrVirtualAttrsOnly(DN entryDN, boolean hasSubs)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("hasSubordinates");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_VIRTUAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(hasSubordinatesType));
  }
  /**
   * Tests the {@code isMultiValued} method.
   */
  @Test()
  public void testIsMultiValued()
  {
    NumSubordinatesVirtualAttributeProvider provider =
        new NumSubordinatesVirtualAttributeProvider();
    assertFalse(provider.isMultiValued());
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProviderTestCase.java
New file
@@ -0,0 +1,645 @@
/*
 * 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 c;
import org.opends.server.types.*;
import org.opends.server.TestCaseUtils;
import org.opends.server.extensions.NumSubordinatesVirtualAttributeProvider;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import static org.opends.server.util.StaticUtils.getBytes;
import static org.opends.server.util.ServerConstants.OID_REAL_ATTRS_ONLY;
import static org.opends.server.util.ServerConstants.OID_VIRTUAL_ATTRS_ONLY;
import org.opends.server.core.DirectoryServer;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertEquals;
import java.util.*;
public class NumSubordinatesVirtualAttributeProviderTestCase
{
    // The attribute type for the numSubordinates attribute.
  private AttributeType numSubordinatesType;
  private List<Entry> entries;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    numSubordinatesType =
        DirectoryServer.getAttributeType("numsubordinates", false);
    assertNotNull(numSubordinatesType);
    entries = TestCaseUtils.makeEntries(
        "dn: dc=example,dc=com",
        "objectclass: top",
        "objectclass: domain",
        "dc: example",
        "",
        "dn: ou=People,dc=example,dc=com",
        "objectclass: top",
        "objectclass: organizationalUnit",
        "ou: People",
        "",
        "dn: ou=Employees,ou=People,dc=example,dc=com",
        "objectclass: top",
        "objectclass: organizationalUnit",
        "ou: Employees",
        "",
        "dn: ou=Buildings,dc=example,dc=com",
        "objectclass: top",
        "objectclass: organizationalUnit",
        "ou: Buildings",
        "",
        "dn: uid=user.0,ou=People,dc=example,dc=com",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "givenName: Aaccf",
        "sn: Amar",
        "cn: Aaccf Amar",
        "initials: AQA",
        "employeeNumber: 0",
        "uid: user.0",
        "mail: user.0@example.com",
        "userPassword: password",
        "telephoneNumber: 380-535-2354",
        "homePhone: 707-626-3913",
        "pager: 456-345-7750",
        "mobile: 366-674-7274",
        "street: 99262 Eleventh Street",
        "l: Salem",
        "st: NM",
        "postalCode: 36530",
        "postalAddress: Aaccf Amar$99262 Eleventh Street$Salem, NM  36530",
        "description: This is the description for Aaccf Amar.",
        "",
        "dn: uid=user.1,ou=People,dc=example,dc=com",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "givenName: Aaren",
        "sn: Atp",
        "cn: Aaren Atp",
        "initials: APA",
        "employeeNumber: 1",
        "uid: user.1",
        "mail: user.1@example.com",
        "userPassword: password",
        "telephoneNumber: 643-278-6134",
        "homePhone: 546-786-4099",
        "pager: 508-261-3187",
        "mobile: 377-267-7824",
        "street: 78113 Fifth Street",
        "l: Chico",
        "st: TN",
        "postalCode: 72322",
        "postalAddress: Aaren Atp$78113 Fifth Street$Chico, TN  72322",
        "description: This is the description for Aaren Atp.",
        "",
        "dn: uid=user.2,ou=Employees,ou=People,dc=example,dc=com",
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "givenName: Aarika",
        "sn: Atpco",
        "cn: Aarika Atpco",
        "initials: ARA",
        "employeeNumber: 2",
        "uid: user.2",
        "mail: user.2@example.com",
        "userPassword: password",
        "telephoneNumber: 547-504-3498",
        "homePhone: 955-899-7308",
        "pager: 710-832-9316",
        "mobile: 688-388-4525",
        "street: 59208 Elm Street",
        "l: Youngstown",
        "st: HI",
        "postalCode: 57377",
        "postalAddress: Aarika Atpco$59208 Elm Street$Youngstown, HI  57377",
        "description: This is the description for Aarika Atpco.");
    TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(entries);
  }
    /**
   * Retrieves a set of entry DNs for use in testing the numSubordinates virtual
   * attribute.
   *
   * @return  A set of entry DNs for use in testing the numSubordinates virtual
   *          attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "testEntryDNs")
  public Object[][] getTestEntryDNs()
         throws Exception
  {
    return new Object[][]
    {
      new Object[] { DN.decode("dc=example,dc=com"), 2 },
      new Object[] { DN.decode("ou=People,dc=example,dc=com"), 3 },
      new Object[] { DN.decode("ou=Employees,ou=People,dc=example,dc=com"), 1 },
      new Object[] { DN.decode("ou=Buildings,dc=example,dc=com"), 0 },
      new Object[] { DN.decode("uid=user.0,ou=People,dc=example,dc=com"), 0 },
      new Object[] { DN.decode("uid=user.1,ou=People,dc=example,dc=com"), 0 },
      new Object[] { DN.decode("uid=user.2,ou=Employees,ou=People" +
                               ",dc=example,dc=com"), 0 },
      new Object[] { DN.decode("cn=monitor"),
          DirectoryServer.getMonitorProviders().size() },
      new Object[] { DN.decode("cn=Backends,cn=config"),
          DirectoryServer.getBackends().size() },
      new Object[] { DN.decode("cn=Work Queue,cn=config"), 0 },
      new Object[] { DN.decode("cn=tasks"), 2 },
      new Object[] { DN.decode("cn=Recurring Tasks,cn=tasks"), 0 },
      new Object[] { DN.decode("cn=Scheduled Tasks,cn=tasks"), 0 },
      new Object[] { DN.decode("cn=backups"), 0 }
    };
  }
  /**
   * Tests the {@code getEntry} method for the specified entry to ensure that
   * the entry returned includes the numSubordinates operational attribute
   * with the correct value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   * @param count The number of subordinates the entry should have.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testGetEntry(DN entryDN, int count)
      throws Exception
  {
    Entry e = DirectoryServer.getEntry(entryDN);
    assertNotNull(e);
    assertTrue(e.hasAttribute(numSubordinatesType));
    List<Attribute> attrList = e.getAttribute(numSubordinatesType);
    assertNotNull(attrList);
    assertFalse(attrList.isEmpty());
    for (Attribute a : attrList)
    {
      assertTrue(a.hasValue());
      assertEquals(a.getValues().size(), 1);
      assertTrue(a.getValues().contains(new AttributeValue(
          ByteStringFactory.create(String.valueOf(count)),
          ByteStringFactory.create(String.valueOf(count)))));
      assertTrue(a.hasValue(new AttributeValue(numSubordinatesType,
                                               String.valueOf(count))));
    }
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the hasSubordinates attribute is not included when the list of attributes
   * requested is empty (defaulting to all user attributes).
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEmptyAttrs(DN entryDN, int count)
      throws Exception
  {
    SearchFilter filter =
        SearchFilter.createFilterFromString("(objectClass=*)");
    InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
        conn.processSearch(entryDN, SearchScope.BASE_OBJECT, filter);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is not included when the list of requested
   * attributes is "1.1", meaning no attributes.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchNoAttrs(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("1.1");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is not included when all user attributes are
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllUserAttrs(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("*");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is included when all operational attributes are
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllOperationalAttrs(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("+");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is included when that attribute is specifically
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchnumSubordinatesAttr(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("numSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is not included when it is not in the list of
   * attributes that is explicitly requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchExcludenumSubordinatesAttr(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("objectClass");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is included when that attribute is specifically
   * requested and the numSubordinates attribute is used in the search filter with a
   * matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchnumSubordinatesAttrInMatchingFilter(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(numSubordinates=" + count + ")");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("numSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * no entries are returned when the numSubordinates attribute is used in the search
   * filter with a non-matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchnumSubordinatesAttrInNonMatchingFilter(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(numSubordinates=wrong)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("numSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 0);
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is not included when that attribute is specifically
   * requested and the real attributes only control is included in the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchnumSubordinatesAttrRealAttrsOnly(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("numSubordinates");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_REAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is included when that attribute is specifically
   * requested and the virtual attributes only control is included
   * in the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchnumSubordinatesAttrVirtualAttrsOnly(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("numSubordinates");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_VIRTUAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is included when that attribute is specifically
   * requested and the numSubordinates attribute is used in the search filter with a
   * greater than value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test
  public void testSearchnumSubordinatesAttrInGTEFilter(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(numSubordinates>=" + count + ")");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("numSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the numSubordinates attribute is included when that attribute is specifically
   * requested and the numSubordinates attribute is used in the search filter with a
   * less than value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test
  public void testSearchnumSubordinatesAttrInLTEFilter(DN entryDN, int count)
         throws Exception
  {
    SearchFilter filter =
         SearchFilter.createFilterFromString("(numSubordinates<=" + count + ")");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("numSubordinates");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(numSubordinatesType));
  }
  /**
   * Tests the {@code isMultiValued} method.
   */
  @Test()
  public void testIsMultiValued()
  {
    NumSubordinatesVirtualAttributeProvider provider =
        new NumSubordinatesVirtualAttributeProvider();
    assertFalse(provider.isMultiValued());
  }
}