From ffb9044301d1c169f934e0adf4f473e99da39a47 Mon Sep 17 00:00:00 2001
From: boli <boli@localhost>
Date: Mon, 27 Aug 2007 18:58:10 +0000
Subject: [PATCH] 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. 

---
 opends/src/server/org/opends/server/backends/task/TaskScheduler.java                                                        |   40 
 opends/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProvider.java                                 |  279 +++
 opends/tests/unit-tests-testng/resource/config-changes.ldif                                                                 |   14 
 opends/src/server/org/opends/server/backends/RootDSEBackend.java                                                            |   76 
 opends/src/messages/messages/extension.properties                                                                           |    4 
 opends/src/server/org/opends/server/backends/SchemaBackend.java                                                             |   75 
 opends/src/server/org/opends/server/backends/jeb/JebFormat.java                                                             |   74 
 opends/src/server/org/opends/server/backends/BackupBackend.java                                                             |  116 +
 opends/src/server/org/opends/server/api/Backend.java                                                                        |   35 
 opends/src/messages/messages/tools.properties                                                                               |   79 +
 opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java                                                         |   11 
 opends/resource/bin/dbtest.bat                                                                                              |   33 
 opends/resource/config/config.ldif                                                                                          |   18 
 opends/src/server/org/opends/server/backends/jeb/Index.java                                                                 |   83 
 opends/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java                                 |  250 +++
 opends/resource/bin/dbtest                                                                                                  |   37 
 opends/src/server/org/opends/server/backends/MemoryBackend.java                                                             |   60 
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProviderTestCase.java |  645 ++++++++
 opends/src/server/org/opends/server/backends/MonitorBackend.java                                                            |   89 
 opends/src/server/org/opends/server/backends/jeb/EntryContainer.java                                                        |   26 
 opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java                                                      |   32 
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProviderTestCase.java |  581 +++++++
 opends/resource/schema/00-core.ldif                                                                                         |    9 
 opends/src/server/org/opends/server/backends/jeb/BackendImpl.java                                                           |  101 +
 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java                               |   48 
 opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java                                                            |  120 +
 opends/src/server/org/opends/server/extensions/ConfigFileHandler.java                                                       |   54 
 opends/src/server/org/opends/server/tools/DBTest.java                                                                       | 1602 ++++++++++++++++++++
 opends/src/server/org/opends/server/backends/task/TaskBackend.java                                                          |   81 
 29 files changed, 4,411 insertions(+), 261 deletions(-)

diff --git a/opends/resource/bin/dbtest b/opends/resource/bin/dbtest
new file mode 100644
index 0000000..99fb90c
--- /dev/null
+++ b/opends/resource/bin/dbtest
@@ -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" "${@}"
diff --git a/opends/resource/bin/dbtest.bat b/opends/resource/bin/dbtest.bat
new file mode 100644
index 0000000..a1beedd
--- /dev/null
+++ b/opends/resource/bin/dbtest.bat
@@ -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" %*
+
diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 66320c6..9e4f1ac 100644
--- a/opends/resource/config/config.ldif
+++ b/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
diff --git a/opends/resource/schema/00-core.ldif b/opends/resource/schema/00-core.ldif
index 588c0a6..979bedf 100644
--- a/opends/resource/schema/00-core.ldif
+++ b/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
diff --git a/opends/src/messages/messages/extension.properties b/opends/src/messages/messages/extension.properties
index 3484c9b..a3cb7b7 100644
--- a/opends/src/messages/messages/extension.properties
+++ b/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
diff --git a/opends/src/messages/messages/tools.properties b/opends/src/messages/messages/tools.properties
index b13a171..2d32c1a 100644
--- a/opends/src/messages/messages/tools.properties
+++ b/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
\ No newline at end of file
diff --git a/opends/src/server/org/opends/server/api/Backend.java b/opends/src/server/org/opends/server/api/Backend.java
index 0d0bff4..4651075 100644
--- a/opends/src/server/org/opends/server/api/Backend.java
+++ b/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
diff --git a/opends/src/server/org/opends/server/backends/BackupBackend.java b/opends/src/server/org/opends/server/backends/BackupBackend.java
index 3993128..0f7332b 100644
--- a/opends/src/server/org/opends/server/backends/BackupBackend.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/backends/MemoryBackend.java b/opends/src/server/org/opends/server/backends/MemoryBackend.java
index 3c3b4d6..f8ac896 100644
--- a/opends/src/server/org/opends/server/backends/MemoryBackend.java
+++ b/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}
diff --git a/opends/src/server/org/opends/server/backends/MonitorBackend.java b/opends/src/server/org/opends/server/backends/MonitorBackend.java
index 5d00097..8e55f48 100644
--- a/opends/src/server/org/opends/server/backends/MonitorBackend.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/backends/RootDSEBackend.java b/opends/src/server/org/opends/server/backends/RootDSEBackend.java
index 8ee8b94..0f5528d 100644
--- a/opends/src/server/org/opends/server/backends/RootDSEBackend.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opends/src/server/org/opends/server/backends/SchemaBackend.java
index 6f63b3e..fab9a47 100644
--- a/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java b/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
index cc8b802..b9a7956 100644
--- a/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
+++ b/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)
     {
diff --git a/opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java b/opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java
index 713e161..3888e04 100644
--- a/opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java
+++ b/opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java
@@ -142,14 +142,15 @@
     {
       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);
-      }
+
     }
+    bufferedValue.isDirty = entryIDList.add(entryID);
   }
 
   /**
diff --git a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java b/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
index 0080ff6..8a66f65 100644
--- a/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java b/opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java
index 76ec3e1..ffa7a74 100644
--- a/opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java
+++ b/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,7 +92,25 @@
       return;
     }
 
-    values = JebFormat.entryIDListFromDatabase(bytes);
+    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);
+    }
+
   }
 
   /**
@@ -105,15 +141,28 @@
     boolean needSort = false;
     int count = 0;
 
+    boolean undefined = false;
     for (EntryIDSet l : sets)
     {
       if (!l.isDefined())
       {
-        return new EntryIDSet();
+        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,7 +249,16 @@
       if (keyBytes != null)
       {
         // The index entry limit was exceeded
-        buffer.append("[LIMIT-EXCEEDED]");
+        if(undefinedSize == Long.MAX_VALUE)
+        {
+          buffer.append("[LIMIT-EXCEEDED]");
+        }
+        else
+        {
+          buffer.append("[LIMIT-EXCEEDED:");
+          buffer.append(undefinedSize);
+          buffer.append("]");
+        }
       }
       else
       {
@@ -232,7 +290,14 @@
    */
   public byte[] toDatabase()
   {
-    return JebFormat.entryIDListToDatabase(values);
+    if(isDefined())
+    {
+      return JebFormat.entryIDListToDatabase(values);
+    }
+    else
+    {
+      return JebFormat.entryIDUndefinedSizeToDatabase(undefinedSize);
+    }
   }
 
   /**
@@ -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;
     }
diff --git a/opends/src/server/org/opends/server/backends/jeb/Index.java b/opends/src/server/org/opends/server/backends/jeb/Index.java
index dee7ea1..e2a8b11 100644
--- a/opends/src/server/org/opends/server/backends/jeb/Index.java
+++ b/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,18 +212,13 @@
 
           }
         }
-        else
-        {
-          if(!entryIDList.add(entryID))
-          {
-            success = false;
-          }
-        }
-
-        byte[] after = entryIDList.toDatabase();
-        data.setData(after);
-        put(txn, key, data);
       }
+
+      success = entryIDList.add(entryID);
+
+      byte[] after = entryIDList.toDatabase();
+      data.setData(after);
+      put(txn, key, data);
     }
     else
     {
@@ -245,7 +240,7 @@
    * @throws DatabaseException If an error occurs in the JE database.
    */
   public void removeID(Transaction txn, DatabaseEntry key, EntryID entryID)
-       throws DatabaseException
+      throws DatabaseException
   {
     OperationStatus status;
     LockMode lockMode = LockMode.RMW;
@@ -256,48 +251,42 @@
     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)
       {
-        // Ignore failures if rebuild is running since the entry ID is
-        // probably already removed.
-        if (!entryIDList.remove(entryID) && !rebuildRunning)
+        if(trusted)
         {
-          // Invalidate the key by setting it undefined
-          byte[] after = new EntryIDSet().toDatabase();
-          data.setData(after);
-          put(txn, key, data);
+          setTrusted(txn, false);
 
-          if(trusted)
+
+
+          if(debugEnabled())
           {
-            setTrusted(txn, false);
-
-            if(debugEnabled())
-            {
-              StringBuilder builder = new StringBuilder();
-              StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
-              TRACER.debugError("The expected entry ID does not exist in " +
-                  "the entry ID list for index %s.\nKey:%s",
-                                name, builder.toString());
-            }
-
-            logError(ERR_JEB_INDEX_CORRUPT_REQUIRES_REBUILD.get(name));
+            StringBuilder builder = new StringBuilder();
+            StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
+            TRACER.debugError("The expected entry ID does not exist in " +
+                "the entry ID list for index %s.\nKey:%s",
+                              name, builder.toString());
           }
+
+          logError(ERR_JEB_INDEX_CORRUPT_REQUIRES_REBUILD.get(name));
+        }
+      }
+      else
+      {
+        byte[] after = entryIDList.toDatabase();
+        if (after == null)
+        {
+          // No more IDs, so remove the key. If index is not
+          // trusted then this will cause all subsequent reads
+          // for this key to return undefined set.
+          delete(txn, key);
         }
         else
         {
-          byte[] after = entryIDList.toDatabase();
-          if (after == null)
-          {
-            // No more IDs, so remove the key. If index is not
-            // trusted then this will cause all subsequent reads
-            // for this key to return undefined set.
-            delete(txn, key);
-          }
-          else
-          {
-            data.setData(after);
-            put(txn, key, data);
-          }
+          data.setData(after);
+          put(txn, key, data);
         }
       }
     }
diff --git a/opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java b/opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java
index 2090df5..f4afb02 100644
--- a/opends/src/server/org/opends/server/backends/jeb/IndexMergeThread.java
+++ b/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
             {
diff --git a/opends/src/server/org/opends/server/backends/jeb/JebFormat.java b/opends/src/server/org/opends/server/backends/jeb/JebFormat.java
index 2e133ae..934a70f 100644
--- a/opends/src/server/org/opends/server/backends/jeb/JebFormat.java
+++ b/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
diff --git a/opends/src/server/org/opends/server/backends/task/TaskBackend.java b/opends/src/server/org/opends/server/backends/task/TaskBackend.java
index a27f5fa..f716e0e 100644
--- a/opends/src/server/org/opends/server/backends/task/TaskBackend.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/backends/task/TaskScheduler.java b/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
index d49d73f..285c8da 100644
--- a/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
+++ b/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();
+    }
+  }
+
 
 
   /**
diff --git a/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java b/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
index 3836371..0712d7a 100644
--- a/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
+++ b/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.
diff --git a/opends/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProvider.java b/opends/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProvider.java
new file mode 100644
index 0000000..1fc5d27
--- /dev/null
+++ b/opends/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProvider.java
@@ -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);
+  }
+}
diff --git a/opends/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java b/opends/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java
new file mode 100644
index 0000000..7aa6fef
--- /dev/null
+++ b/opends/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProvider.java
@@ -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);
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/DBTest.java b/opends/src/server/org/opends/server/tools/DBTest.java
new file mode 100644
index 0000000..d269c81
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/DBTest.java
@@ -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));
+  }
+}
diff --git a/opends/tests/unit-tests-testng/resource/config-changes.ldif b/opends/tests/unit-tests-testng/resource/config-changes.ldif
index 79f76b5..c751436 100644
--- a/opends/tests/unit-tests-testng/resource/config-changes.ldif
+++ b/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
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
index 8e2b059..9e0223b 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
+++ b/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.
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProviderTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..42213b1
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/HasSubordinatesVirtualAttributeProviderTestCase.java
@@ -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());
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProviderTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..776be72
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/NumSubordinatesVirtualAttributeProviderTestCase.java
@@ -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());
+  }
+}

--
Gitblit v1.10.0