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