From 6912362c6715306768c8fc14dd18ad9293e4c2ca Mon Sep 17 00:00:00 2001
From: abobrov <abobrov@localhost>
Date: Fri, 25 Dec 2009 18:28:32 +0000
Subject: [PATCH] - land Subentry Manager and Collective Attributes implementations; Merry XMAS to yall!

---
 opends/resource/schema/02-config.ldif                                                                            |    5 
 opends/src/messages/messages/core.properties                                                                     |    3 
 opends/src/server/org/opends/server/types/Entry.java                                                             |  264 ++++++++
 opends/src/server/org/opends/server/extensions/CollectiveAttributeSubentriesVirtualAttributeProvider.java        |  153 +++++
 opends/src/messages/messages/schema.properties                                                                   |    6 
 opends/src/server/org/opends/server/core/SubentryManager.java                                                    |  647 +++++++++++++++++++++
 opends/src/server/org/opends/server/types/CollectiveVirtualAttribute.java                                        |  131 ++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AttributeTypeSyntaxTest.java                  |    6 
 opends/resource/config/config.ldif                                                                               |   10 
 opends/src/server/org/opends/server/types/SubEntry.java                                                          |  281 +++++++++
 opends/src/server/org/opends/server/util/ServerConstants.java                                                    |   92 +++
 opends/src/server/org/opends/server/core/DirectoryServer.java                                                    |   22 
 opends/src/server/org/opends/server/core/SearchOperationBasis.java                                               |    8 
 opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java                                              |   25 
 opends/resource/schema/00-core.ldif                                                                              |   43 +
 opends/src/messages/messages/extension.properties                                                                |    3 
 opends/src/admin/defn/org/opends/server/admin/std/CollectiveAttributeSubentriesVirtualAttributeConfiguration.xml |   70 ++
 17 files changed, 1,737 insertions(+), 32 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 8d1599f..a9ad022 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -2460,6 +2460,16 @@
 ds-cfg-filter: (&(objectClass=groupOfUniqueNames)(objectClass=ds-virtual-static-group))
 ds-cfg-allow-retrieving-membership: false
 
+dn: cn=Collective Attribute Subentries,cn=Virtual Attributes,cn=config
+objectClass: top
+objectClass: ds-cfg-virtual-attribute
+objectClass: ds-cfg-collective-attribute-subentries-virtual-attribute
+cn: Collective Attribute Subentries
+ds-cfg-java-class: org.opends.server.extensions.CollectiveAttributeSubentriesVirtualAttributeProvider
+ds-cfg-enabled: true
+ds-cfg-attribute-type: collectiveAttributeSubentries
+ds-cfg-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 606a8ad..c8b66a8 100644
--- a/opends/resource/schema/00-core.ldif
+++ b/opends/resource/schema/00-core.ldif
@@ -381,6 +381,43 @@
 attributeTypes: ( 1.3.6.1.4.1.7628.5.4.2 NAME 'blockInheritance'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE NO-USER-MODIFICATION
   USAGE dSAOperation X-ORIGIN 'draft-ietf-ldup-subentry' )
+attributeTypes: ( 2.5.18.6 NAME 'subtreeSpecification'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.45 SINGLE-VALUE
+  USAGE directoryOperation X-ORIGIN 'RFC 3672' )
+attributeTypes: ( 2.5.18.12 NAME 'collectiveAttributeSubentries'
+  EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  USAGE directoryOperation NO-USER-MODIFICATION X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.18.7 NAME 'collectiveExclusions'
+  EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38
+  USAGE directoryOperation X-ORIGIN 'RFC 3671' )
+ldapSyntaxes: ( 1.3.6.1.4.1.26027.1.3.6 DESC 'Collective Conflict Behavior'
+  X-ENUM ( 'real-overrides-virtual' 'virtual-overrides-real'
+  'merge-real-and-virtual' ) )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.606
+  NAME 'collectiveConflictBehavior' SYNTAX 1.3.6.1.4.1.26027.1.3.6
+  SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 2.5.4.7.1 NAME 'c-l' SUP l COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.8.1 NAME 'c-st' SUP st COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.9.1 NAME 'c-street' SUP street COLLECTIVE
+  X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.10.1 NAME 'c-o' SUP o COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.11.1 NAME 'c-ou' SUP ou COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.16.1 NAME 'c-PostalAddress' SUP postalAddress
+  COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.17.1 NAME 'c-PostalCode' SUP postalCode COLLECTIVE
+  X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.18.1 NAME 'c-PostOfficeBox' SUP postOfficeBox
+  COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.19.1 NAME 'c-PhysicalDeliveryOfficeName'
+  SUP physicalDeliveryOfficeName COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.20.1 NAME 'c-TelephoneNumber' SUP telephoneNumber
+  COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.21.1 NAME 'c-TelexNumber' SUP telexNumber COLLECTIVE
+  X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.23.1 NAME 'c-FacsimileTelephoneNumber'
+  SUP facsimileTelephoneNumber COLLECTIVE X-ORIGIN 'RFC 3671' )
+attributeTypes: ( 2.5.4.25.1 NAME 'c-InternationalISDNNumber'
+  SUP internationalISDNNumber COLLECTIVE X-ORIGIN 'RFC 3671' )
 attributeTypes: ( 2.16.840.1.113730.3.1.55 NAME 'aci'
   DESC 'Sun-defined access control information attribute type'
   EQUALITY octetStringMatch
@@ -613,6 +650,12 @@
   DESC 'Inheritable LDAP Subentry class, version 1'  SUP ldapSubEntry
   STRUCTURAL  MUST ( inheritable )  MAY  ( blockInheritance )
   X-ORIGIN 'draft-ietf-ldup-subentry' )
+objectClasses: ( 2.5.17.0 NAME 'subentry'
+  DESC 'LDAP Subentry class' SUP top STRUCTURAL MUST ( cn $
+  subtreeSpecification ) X-ORIGIN 'RFC 3672' )
+objectClasses: ( 2.5.17.2 NAME 'collectiveAttributeSubentry'
+  DESC 'LDAP Collective Attributes Subentry class' AUXILIARY
+  X-ORIGIN 'RFC 3671' )
 objectClasses: ( 2.16.840.1.113730.3.2.33 NAME 'groupOfURLs'
   DESC 'Sun-defined objectclass' SUP top STRUCTURAL MUST ( cn )
   MAY ( memberURL $ businessCategory $ description $ o $ ou $ owner $ seeAlso )
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 05ec7bf..a35daf3 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -4151,4 +4151,9 @@
          ds-cfg-enabled )
   MAY  ( ds-cfg-ecl-include )
   X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.235
+  NAME 'ds-cfg-collective-attribute-subentries-virtual-attribute'
+  SUP ds-cfg-virtual-attribute
+  STRUCTURAL
+  X-ORIGIN 'OpenDS Directory Server' )
 
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/CollectiveAttributeSubentriesVirtualAttributeConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/CollectiveAttributeSubentriesVirtualAttributeConfiguration.xml
new file mode 100644
index 0000000..57eab55
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/CollectiveAttributeSubentriesVirtualAttributeConfiguration.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ! 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
+  !
+  !
+  !      Copyright 2009 Sun Microsystems, Inc.
+  ! -->
+<adm:managed-object name="collective-attribute-subentries-virtual-attribute"
+  plural-name="collective-attribute-subentries-virtual-attributes"
+  package="org.opends.server.admin.std" extends="virtual-attribute"
+  xmlns:adm="http://www.opends.org/admin"
+  xmlns:ldap="http://www.opends.org/admin-ldap">
+  <adm:synopsis>
+    The
+    <adm:user-friendly-name />
+    generates a virtual attribute that specifies all collective
+    attribute subentries that affect the entry.
+  </adm:synopsis>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>
+        ds-cfg-collective-attribute-subentries-virtual-attribute
+      </ldap:name>
+      <ldap:superior>ds-cfg-virtual-attribute</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property-override name="java-class" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.extensions.CollectiveAttributeSubentriesVirtualAttributeProvider
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  <adm:property-override name="conflict-behavior" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>virtual-overrides-real</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  <adm:property-override name="attribute-type">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>collectiveAttributeSubentries</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+</adm:managed-object>
diff --git a/opends/src/messages/messages/core.properties b/opends/src/messages/messages/core.properties
index 686477e..17052e4 100644
--- a/opends/src/messages/messages/core.properties
+++ b/opends/src/messages/messages/core.properties
@@ -1825,3 +1825,6 @@
 60 seconds. This option cannot be used with the -N, --nodetach option
 FATAL_ERR_DSCORE_ERROR_NODETACH_TIMEOUT_723=In no-detach mode, the 'timeout' \
 option cannot be used
+SEVERE_WARN_SUBENTRY_FILTER_NOT_INDEXED_724=The search filter "%s" used by \
+ subentry manager is not indexed in backend %s.  Backend initialization \
+ for subentry manager processing might take a very long time to complete
diff --git a/opends/src/messages/messages/extension.properties b/opends/src/messages/messages/extension.properties
index e63b8aa..9f200ea 100644
--- a/opends/src/messages/messages/extension.properties
+++ b/opends/src/messages/messages/extension.properties
@@ -1422,3 +1422,6 @@
 INFO_GSSAPI_STARTED_574=The GSSAPI SASL mechanism handler initialization \
 was successful
 INFO_GSSAPI_STOPPED_575=The GSSAPI SASL mechanism handler has been stopped
+MILD_ERR_COLLECTIVEATTRIBUTESUBENTRIES_VATTR_NOT_SEARCHABLE_576=The %s \
+ attribute is not searchable and should not be included in otherwise \
+ unindexed search filters
diff --git a/opends/src/messages/messages/schema.properties b/opends/src/messages/messages/schema.properties
index 2fff3d5..15c5608 100644
--- a/opends/src/messages/messages/schema.properties
+++ b/opends/src/messages/messages/schema.properties
@@ -881,9 +881,6 @@
 SEVERE_WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_USAGE_268=The definition \
  for attribute type %s is invalid because its attribute usage %s is not the \
  same as the usage for its superior type %s
-SEVERE_WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_FROM_NONCOLLECTIVE_269=The \
- definition for attribute type %s is invalid because it is defined as a \
- collective type but the superior type %s is not collective
 SEVERE_WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE_270=The \
  definition for attribute type %s is invalid because it is not defined as a \
  collective type but the superior type %s is collective
@@ -893,9 +890,6 @@
 MILD_ERR_ATTR_SYNTAX_DCR_PROHIBITED_REQUIRED_BY_AUXILIARY_272=The DIT content \
  rule "%s" is not valid because it prohibits the use of attribute type %s \
  which is required by the associated auxiliary object class %s
-SEVERE_WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_IS_OPERATIONAL_273=The definition \
- for attribute type %s is invalid because it is declared COLLECTIVE but does \
- not have a usage of userApplications
 SEVERE_WARN_ATTR_SYNTAX_ATTRTYPE_NO_USER_MOD_NOT_OPERATIONAL_274=The \
  definition for attribute type %s is invalid because it is declared \
  NO-USER-MODIFICATION but does not have an operational usage
diff --git a/opends/src/server/org/opends/server/core/DirectoryServer.java b/opends/src/server/org/opends/server/core/DirectoryServer.java
index 62d23f2..3f3659d 100644
--- a/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -594,6 +594,9 @@
   // The group manager for the Directory Server.
   private GroupManager groupManager;
 
+  // The subentry manager for the Directory Server.
+  private SubentryManager subentryManager;
+
   // The configuration manager for identity mappers.
   private IdentityMapperConfigManager identityMapperConfigManager;
 
@@ -1420,6 +1423,13 @@
       rootDNConfigManager.initializeRootDNs();
 
 
+      // Initialize the subentry manager.
+      subentryManager = new SubentryManager();
+      // The configuration backend has already been registered at this point
+      // so we need to handle it explicitly.
+      subentryManager.performBackendInitializationProcessing(configHandler);
+
+
       // Initialize the group manager.
       initializeGroupManager();
 
@@ -2671,6 +2681,18 @@
 
 
   /**
+   * Retrieves the Directory Server subentry manager.
+   *
+   * @return  The Directory Server subentry manager.
+   */
+  public static SubentryManager getSubentryManager()
+  {
+    return directoryServer.subentryManager;
+  }
+
+
+
+  /**
    * Initializes the set of supported controls for the Directory Server.
    *
    * @throws  ConfigException  If there is a configuration problem with the
diff --git a/opends/src/server/org/opends/server/core/SearchOperationBasis.java b/opends/src/server/org/opends/server/core/SearchOperationBasis.java
index 5a8d35f..d1d2ba1 100644
--- a/opends/src/server/org/opends/server/core/SearchOperationBasis.java
+++ b/opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -609,7 +609,7 @@
 
     // Determine whether the provided entry is a subentry and if so whether it
     // should be returned.
-    if (entry.isLDAPSubentry())
+    if (entry.isSubentry() || entry.isLDAPSubentry())
     {
       if ((getScope() != SearchScope.BASE_OBJECT) &&
               (! isReturnLDAPSubentries()))
@@ -1494,7 +1494,11 @@
       if (filter.getAttributeType().isObjectClassType())
       {
         AttributeValue v = filter.getAssertionValue();
-        if (toLowerCase(v.getValue().toString()).equals("ldapsubentry"))
+        // FIXME : technically this is not correct since the presense
+        // of draft oc would trigger rfc oc visibility and visa versa.
+        String stringValueLC = toLowerCase(v.getValue().toString());
+        if (stringValueLC.equals(OC_LDAP_SUBENTRY_LC) ||
+            stringValueLC.equals(OC_SUBENTRY))
         {
           setReturnLDAPSubentries(true);
         }
diff --git a/opends/src/server/org/opends/server/core/SubentryManager.java b/opends/src/server/org/opends/server/core/SubentryManager.java
new file mode 100644
index 0000000..008abcf
--- /dev/null
+++ b/opends/src/server/org/opends/server/core/SubentryManager.java
@@ -0,0 +1,647 @@
+/*
+ * 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
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.core;
+
+
+
+import java.util.*;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.opends.server.api.Backend;
+import org.opends.server.api.BackendInitializationListener;
+import org.opends.server.api.ChangeNotificationListener;
+import org.opends.server.controls.SubentriesControl;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.types.Control;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchScope;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SubEntry;
+import org.opends.server.types.operation.PostResponseAddOperation;
+import org.opends.server.types.operation.PostResponseDeleteOperation;
+import org.opends.server.types.operation.PostResponseModifyOperation;
+import org.opends.server.types.operation.PostResponseModifyDNOperation;
+import org.opends.server.workflowelement.localbackend.
+            LocalBackendSearchOperation;
+
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.loggers.ErrorLogger.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.config.ConfigConstants.*;
+
+
+
+/**
+ * This class provides a mechanism for interacting with subentries defined in
+ * the Directory Server.  It will handle all necessary processing at server
+ * startup to identify and load subentries within the server.
+ * <BR><BR>
+ * FIXME:  At the present time, it assumes that all of the necessary
+ * information about subentries defined in the server can be held in
+ * memory.  If it is determined that this approach is not workable
+ * in all cases, then we will need an alternate strategy.
+ */
+public class SubentryManager
+        implements BackendInitializationListener, ChangeNotificationListener
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  // A mapping between the DNs and applicable subentries.
+  private HashMap<DN,List<SubEntry>> dn2SubEntry;
+
+  // A mapping between the DNs and applicable collective subentries.
+  private HashMap<DN,List<SubEntry>> dn2CollectiveSubEntry;
+
+  // Internal search all operational attributes.
+  private LinkedHashSet<String> requestAttrs;
+
+  // Lock to protect internal data structures.
+  private final ReentrantReadWriteLock lock;
+
+  /**
+   * Creates a new instance of this group manager.
+   */
+  public SubentryManager()
+  {
+    lock = new ReentrantReadWriteLock();
+
+    dn2SubEntry = new HashMap<DN,List<SubEntry>>();
+    dn2CollectiveSubEntry = new HashMap<DN,List<SubEntry>>();
+
+    requestAttrs = new LinkedHashSet<String>();
+    requestAttrs.add("subtreespecification");
+    requestAttrs.add("*");
+
+    DirectoryServer.registerBackendInitializationListener(this);
+    DirectoryServer.registerChangeNotificationListener(this);
+  }
+
+  /**
+   * Add a given entry to this subentry manager.
+   * @param entry to add.
+   */
+  private void addSubEntry(Entry entry) throws DirectoryException
+  {
+    SubEntry subEntry = new SubEntry(entry);
+    RFC3672SubtreeSpecification subSpec =
+            subEntry.getSubTreeSpecification();
+    DN subDN = subSpec.getBaseDN();
+    List<SubEntry> subList = null;
+    lock.writeLock().lock();
+    try
+    {
+      if (subEntry.isCollective())
+      {
+        subList = dn2CollectiveSubEntry.get(subDN);
+      }
+      else
+      {
+        subList = dn2SubEntry.get(subDN);
+      }
+      if (subList == null)
+      {
+        subList = new ArrayList<SubEntry>();
+        if (subEntry.isCollective())
+        {
+          dn2CollectiveSubEntry.put(subDN, subList);
+        }
+        else
+        {
+          dn2SubEntry.put(subDN, subList);
+        }
+      }
+      subList.add(subEntry);
+    }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
+  }
+
+  /**
+   * Remove a given entry from this subentry manager.
+   * @param entry to remove.
+   */
+  private void removeSubEntry(Entry entry)
+  {
+    lock.writeLock().lock();
+    try
+    {
+      boolean removed = false;
+      Iterator<Map.Entry<DN, List<SubEntry>>> iterator =
+              dn2SubEntry.entrySet().iterator();
+      while (iterator.hasNext())
+      {
+        Map.Entry<DN, List<SubEntry>> mapEntry = iterator.next();
+        List<SubEntry> subList = mapEntry.getValue();
+        for (SubEntry subEntry : subList)
+        {
+          if (subEntry.getDN().equals(entry.getDN()))
+          {
+            removed = subList.remove(subEntry);
+            break;
+          }
+        }
+        if (subList.isEmpty())
+        {
+          iterator.remove();
+        }
+        if (removed)
+        {
+          return;
+        }
+      }
+      iterator = dn2CollectiveSubEntry.entrySet().iterator();
+      while (iterator.hasNext())
+      {
+        Map.Entry<DN, List<SubEntry>> mapEntry = iterator.next();
+        List<SubEntry> subList = mapEntry.getValue();
+        for (SubEntry subEntry : subList)
+        {
+          if (subEntry.getDN().equals(entry.getDN()))
+          {
+            removed = subList.remove(subEntry);
+            break;
+          }
+        }
+        if (subList.isEmpty())
+        {
+          iterator.remove();
+        }
+        if (removed)
+        {
+          return;
+        }
+      }
+    }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
+  }
+
+  /**
+   * {@inheritDoc}  In this case, the server will search the backend to find
+   * all subentries that it may contain and register them with this manager.
+   */
+  public void performBackendInitializationProcessing(Backend backend)
+  {
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    LinkedList<Control> requestControls = new LinkedList<Control>();
+    requestControls.add(new SubentriesControl(true, true));
+
+    SearchFilter filter = null;
+    try
+    {
+      filter = SearchFilter.createFilterFromString("(" +
+            ATTR_OBJECTCLASS + "=" + OC_SUBENTRY + ")");
+      if (backend.getEntryCount() > 0 && ! backend.isIndexed(filter))
+      {
+        logError(WARN_SUBENTRY_FILTER_NOT_INDEXED.get(
+                String.valueOf(filter), backend.getBackendID()));
+      }
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+
+    for (DN baseDN : backend.getBaseDNs())
+    {
+      try
+      {
+        if (! backend.entryExists(baseDN))
+        {
+          continue;
+        }
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        // FIXME -- Is there anything that we need to do here?
+        continue;
+      }
+
+      InternalSearchOperation internalSearch = new InternalSearchOperation(
+              conn, InternalClientConnection.nextOperationID(),
+              InternalClientConnection.nextMessageID(),
+              requestControls, baseDN, SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+              filter, requestAttrs, null);
+      LocalBackendSearchOperation localSearch =
+              new LocalBackendSearchOperation(internalSearch);
+
+      try
+      {
+        backend.search(localSearch);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        // FIXME -- Is there anything that we need to do here?
+        continue;
+      }
+
+      for (SearchResultEntry entry : internalSearch.getSearchEntries())
+      {
+        if (entry.isSubentry())
+        {
+          try
+          {
+            addSubEntry(entry);
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            // FIXME -- Handle this.
+            continue;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Return subentries applicable to specific DN.
+   * Note that this getter will skip any collective subentries,
+   * returning only applicable regular subentries.
+   * @param  dn for which to retrieve applicable
+   *         subentries.
+   * @return applicable subentries.
+   */
+  public List<SubEntry> getSubentries(DN dn)
+  {
+    if (dn2SubEntry.isEmpty())
+    {
+      return Collections.emptyList();
+    }
+
+    List<SubEntry> subentries = new ArrayList<SubEntry>();
+
+    lock.readLock().lock();
+    try
+    {
+      for (DN subDN = dn; subDN != null;
+           subDN = subDN.getParent())
+      {
+        List<SubEntry> subList = dn2SubEntry.get(subDN);
+        if (subList != null)
+        {
+          for (SubEntry subEntry : subList)
+          {
+            RFC3672SubtreeSpecification subSpec =
+                    subEntry.getSubTreeSpecification();
+            if (subSpec.isDNWithinScope(dn))
+            {
+              subentries.add(subEntry);
+            }
+          }
+        }
+      }
+    }
+    finally
+    {
+      lock.readLock().unlock();
+    }
+
+    return subentries;
+  }
+
+  /**
+   * Return subentries applicable to specific entry.
+   * Note that this getter will skip any collective subentries,
+   * returning only applicable regular subentries.
+   * @param  entry for which to retrieve applicable
+   *         subentries.
+   * @return applicable subentries.
+   */
+  public List<SubEntry> getSubentries(Entry entry)
+  {
+    if (dn2SubEntry.isEmpty())
+    {
+      return Collections.emptyList();
+    }
+
+    List<SubEntry> subentries = new ArrayList<SubEntry>();
+
+    lock.readLock().lock();
+    try
+    {
+      for (DN subDN = entry.getDN(); subDN != null;
+           subDN = subDN.getParent())
+      {
+        List<SubEntry> subList = dn2SubEntry.get(subDN);
+        if (subList != null)
+        {
+          for (SubEntry subEntry : subList)
+          {
+            RFC3672SubtreeSpecification subSpec =
+                    subEntry.getSubTreeSpecification();
+            if (subSpec.isWithinScope(entry))
+            {
+              subentries.add(subEntry);
+            }
+          }
+        }
+      }
+    }
+    finally
+    {
+      lock.readLock().unlock();
+    }
+
+    return subentries;
+  }
+
+  /**
+   * Return collective subentries applicable to specific DN.
+   * Note that this getter will skip any regular subentries,
+   * returning only applicable collective subentries.
+   * @param  dn for which to retrieve applicable
+   *         subentries.
+   * @return applicable subentries.
+   */
+  public List<SubEntry> getCollectiveSubentries(DN dn)
+  {
+    if (dn2CollectiveSubEntry.isEmpty())
+    {
+      return Collections.emptyList();
+    }
+
+    List<SubEntry> subentries = new ArrayList<SubEntry>();
+
+    lock.readLock().lock();
+    try
+    {
+      for (DN subDN = dn; subDN != null;
+           subDN = subDN.getParent())
+      {
+        List<SubEntry> subList = dn2CollectiveSubEntry.get(subDN);
+        if (subList != null)
+        {
+          for (SubEntry subEntry : subList)
+          {
+            RFC3672SubtreeSpecification subSpec =
+                    subEntry.getSubTreeSpecification();
+            if (subSpec.isDNWithinScope(dn))
+            {
+              subentries.add(subEntry);
+            }
+          }
+        }
+      }
+    }
+    finally
+    {
+      lock.readLock().unlock();
+    }
+
+    return subentries;
+  }
+
+  /**
+   * Return collective subentries applicable to specific entry.
+   * Note that this getter will skip any regular subentries,
+   * returning only applicable collective subentries.
+   * @param  entry for which to retrieve applicable
+   *         subentries.
+   * @return applicable subentries.
+   */
+  public List<SubEntry> getCollectiveSubentries(Entry entry)
+  {
+    if (dn2CollectiveSubEntry.isEmpty())
+    {
+      return Collections.emptyList();
+    }
+
+    List<SubEntry> subentries = new ArrayList<SubEntry>();
+
+    lock.readLock().lock();
+    try
+    {
+      for (DN subDN = entry.getDN(); subDN != null;
+           subDN = subDN.getParent())
+      {
+        List<SubEntry> subList = dn2CollectiveSubEntry.get(subDN);
+        if (subList != null)
+        {
+          for (SubEntry subEntry : subList)
+          {
+            RFC3672SubtreeSpecification subSpec =
+                    subEntry.getSubTreeSpecification();
+            if (subSpec.isWithinScope(entry))
+            {
+              subentries.add(subEntry);
+            }
+          }
+        }
+      }
+    }
+    finally
+    {
+      lock.readLock().unlock();
+    }
+
+    return subentries;
+  }
+
+  /**
+   * {@inheritDoc}  In this case, the server will de-register
+   * all subentries associated with the provided backend.
+   */
+  public void performBackendFinalizationProcessing(Backend backend)
+  {
+    lock.writeLock().lock();
+    try
+    {
+      Iterator<Map.Entry<DN, List<SubEntry>>> iterator =
+              dn2SubEntry.entrySet().iterator();
+      while (iterator.hasNext())
+      {
+        Map.Entry<DN, List<SubEntry>> mapEntry = iterator.next();
+        List<SubEntry> subList = mapEntry.getValue();
+        for (SubEntry subEntry : subList)
+        {
+          if (backend.handlesEntry(subEntry.getDN()))
+          {
+            subList.remove(subEntry);
+          }
+        }
+        if (subList.isEmpty())
+        {
+          iterator.remove();
+        }
+      }
+      iterator = dn2CollectiveSubEntry.entrySet().iterator();
+      while (iterator.hasNext())
+      {
+        Map.Entry<DN, List<SubEntry>> mapEntry = iterator.next();
+        List<SubEntry> subList = mapEntry.getValue();
+        for (SubEntry subEntry : subList)
+        {
+          if (backend.handlesEntry(subEntry.getDN()))
+          {
+            subList.remove(subEntry);
+          }
+        }
+        if (subList.isEmpty())
+        {
+          iterator.remove();
+        }
+      }
+    }
+    finally
+    {
+      lock.writeLock().unlock();
+    }
+  }
+
+  /**
+   * {@inheritDoc}  In this case, each entry is checked to see if it is
+   * a subentry, and if so it will be registered with this manager.
+   */
+  public void handleAddOperation(PostResponseAddOperation addOperation,
+                                 Entry entry)
+  {
+    if (entry.isSubentry())
+    {
+      try
+      {
+        addSubEntry(entry);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        // FIXME -- Handle this.
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}  In this case, each entry is checked to see if it is
+   * a subentry, and if so it will be deregistered with this manager.
+   */
+  public void handleDeleteOperation(PostResponseDeleteOperation deleteOperation,
+                                    Entry entry)
+  {
+    if (entry.isSubentry())
+    {
+      removeSubEntry(entry);
+    }
+  }
+
+  /**
+   * {@inheritDoc}  In this case, if the entry is a registered subentry
+   * then it will be recreated from the contents of the provided entry
+   * and re-registered with this manager.
+   */
+  public void handleModifyOperation(PostResponseModifyOperation modifyOperation,
+                                    Entry oldEntry, Entry newEntry)
+  {
+    if (oldEntry.isSubentry())
+    {
+      removeSubEntry(oldEntry);
+    }
+    if (newEntry.isSubentry())
+    {
+      try
+      {
+        addSubEntry(newEntry);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        // FIXME -- Handle this.
+      }
+    }
+  }
+
+  /**
+   * {@inheritDoc}  In this case, if the subentry is registered then it
+   * will be recreated from the contents of the provided entry and re-
+   * registered with this manager under the new DN and the old instance
+   * will be deregistered.
+   */
+  public void handleModifyDNOperation(
+                   PostResponseModifyDNOperation modifyDNOperation,
+                   Entry oldEntry, Entry newEntry)
+  {
+    if (oldEntry.isSubentry())
+    {
+      removeSubEntry(oldEntry);
+      try
+      {
+        addSubEntry(newEntry);
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        // FIXME -- Handle this.
+      }
+    }
+  }
+}
diff --git a/opends/src/server/org/opends/server/extensions/CollectiveAttributeSubentriesVirtualAttributeProvider.java b/opends/src/server/org/opends/server/extensions/CollectiveAttributeSubentriesVirtualAttributeProvider.java
new file mode 100644
index 0000000..c8d9b37
--- /dev/null
+++ b/opends/src/server/org/opends/server/extensions/CollectiveAttributeSubentriesVirtualAttributeProvider.java
@@ -0,0 +1,153 @@
+/*
+ * 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
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.server.extensions;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.
+        CollectiveAttributeSubentriesVirtualAttributeCfg;
+import org.opends.server.api.VirtualAttributeProvider;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.config.ConfigException;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.*;
+
+import static org.opends.messages.ExtensionMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+/**
+ * This class implements a virtual attribute provider to serve the
+ * collectiveAttributeSubentries operational attribute as described
+ * in RFC 3671.
+ */
+public class CollectiveAttributeSubentriesVirtualAttributeProvider
+        extends VirtualAttributeProvider<
+        CollectiveAttributeSubentriesVirtualAttributeCfg>
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  /**
+   * Creates a new instance of this HasSubordinates virtual attribute provider.
+   */
+  public CollectiveAttributeSubentriesVirtualAttributeProvider()
+  {
+    super();
+
+    // All initialization should be performed in the
+    // initializeVirtualAttributeProvider method.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void initializeVirtualAttributeProvider(
+          CollectiveAttributeSubentriesVirtualAttributeCfg configuration)
+          throws ConfigException, InitializationException
+  {
+    // No initialization is required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isMultiValued()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public Set<AttributeValue> getValues(Entry entry,
+                                       VirtualAttributeRule rule)
+  {
+    Set<AttributeValue> valueSet = new HashSet<AttributeValue>();
+    List<SubEntry> subentries =
+            DirectoryServer.getSubentryManager().getCollectiveSubentries(entry);
+
+    AttributeType dnAttrType =
+            DirectoryServer.getAttributeType("2.5.4.49");
+    for (SubEntry subentry : subentries)
+    {
+      if (subentry.isCollective())
+      {
+        DN subentryDN = subentry.getDN();
+        AttributeValue value = AttributeValues.create(
+                dnAttrType, subentryDN.toString());
+        valueSet.add(value);
+      }
+    }
+
+    return valueSet;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @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_COLLECTIVEATTRIBUTESUBENTRIES_VATTR_NOT_SEARCHABLE.get(
+            rule.getAttributeType().getNameOrOID());
+    searchOperation.appendErrorMessage(message);
+  }
+}
diff --git a/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java b/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java
index 579826b..b31af2e 100644
--- a/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java
+++ b/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java
@@ -983,35 +983,20 @@
         throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
       }
 
-      if (superiorType.isCollective() != isCollective)
+      if (superiorType.isCollective())
       {
-        Message message;
-        if (isCollective)
+        if (!isCollective)
         {
-          message = WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_FROM_NONCOLLECTIVE.get(
-                  oid, superiorType.getNameOrOID());
-        }
-        else
-        {
-          message =
+          Message message =
                   WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE.get(
                     oid, superiorType.getNameOrOID());
+          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+                  message);
         }
-        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
       }
     }
 
 
-    // If the attribute type is COLLECTIVE, then it must have a usage of
-    // userApplications.
-    if (isCollective && (attributeUsage != AttributeUsage.USER_APPLICATIONS))
-    {
-      Message message =
-          WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_IS_OPERATIONAL.get(oid);
-      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
-    }
-
-
     // If the attribute type is NO-USER-MODIFICATION, then it must not have a
     // usage of userApplications.
     if (isNoUserModification &&
diff --git a/opends/src/server/org/opends/server/types/CollectiveVirtualAttribute.java b/opends/src/server/org/opends/server/types/CollectiveVirtualAttribute.java
new file mode 100644
index 0000000..68e3489
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/CollectiveVirtualAttribute.java
@@ -0,0 +1,131 @@
+/*
+ * 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
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.server.types;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class defines a collective virtual attribute, which is a
+ * special kind of attribute whose values do not actually exist
+ * in persistent storage but rather are obtained dynamically
+ * from applicable collective attribute subentry.
+ */
+public class CollectiveVirtualAttribute extends AbstractAttribute
+{
+  // The attribute this collective virtual attribute is based on.
+  private Attribute attribute;
+
+  /**
+   * Creates a new collective virtual attribute.
+   * @param attribute The attribute this collective
+   *                  virtual attribute is based on.
+   */
+  public CollectiveVirtualAttribute(Attribute attribute) {
+    this.attribute = attribute;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConditionResult approximatelyEqualTo(AttributeValue value) {
+    return attribute.approximatelyEqualTo(value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean contains(AttributeValue value) {
+    return attribute.contains(value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public AttributeType getAttributeType() {
+    return attribute.getAttributeType();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Set<String> getOptions() {
+    return attribute.getOptions();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConditionResult greaterThanOrEqualTo(AttributeValue value) {
+    return attribute.greaterThanOrEqualTo(value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isVirtual() {
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterator<AttributeValue> iterator() {
+    return attribute.iterator();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConditionResult lessThanOrEqualTo(AttributeValue value) {
+    return attribute.lessThanOrEqualTo(value);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConditionResult matchesSubstring(ByteString subInitial,
+          List<ByteString> subAny, ByteString subFinal) {
+    return attribute.matchesSubstring(subInitial, subAny, subFinal);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int size() {
+    return attribute.size();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void toString(StringBuilder buffer) {
+    attribute.toString(buffer);
+  }
+}
diff --git a/opends/src/server/org/opends/server/types/Entry.java b/opends/src/server/org/opends/server/types/Entry.java
index 2942527..98350a6 100644
--- a/opends/src/server/org/opends/server/types/Entry.java
+++ b/opends/src/server/org/opends/server/types/Entry.java
@@ -1763,7 +1763,10 @@
     {
       if (a.optionsEqual(options))
       {
-        return a.contains(value);
+        if (a.contains(value))
+        {
+          return true;
+        }
       }
     }
 
@@ -3304,6 +3307,92 @@
 
 
   /**
+   * Indicates whether this entry meets the criteria to consider it
+   * an RFC 3672 LDAP subentry (i.e., it contains the "subentry"
+   * objectclass).
+   *
+   * @return  <CODE>true</CODE> if this entry meets the criteria to
+   *          consider it an RFC 3672 LDAP subentry, or <CODE>false
+   *          </CODE> if not.
+   */
+  public boolean isSubentry()
+  {
+    ObjectClass subentryOC =
+         DirectoryServer.getObjectClass(OC_SUBENTRY);
+    if (subentryOC == null)
+    {
+      // This should not happen -- The server doesn't
+      // have a subentry objectclass defined.
+      if (debugEnabled())
+      {
+        TRACER.debugWarning(
+            "No %s objectclass is defined in the server schema.",
+                     OC_SUBENTRY);
+      }
+
+      for (String ocName : objectClasses.values())
+      {
+        if (ocName.equalsIgnoreCase(OC_SUBENTRY))
+        {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+
+    // Make the determination based on whether this
+    // entry has the subentry objectclass.
+    return objectClasses.containsKey(subentryOC);
+  }
+
+
+
+  /**
+   * Indicates whether the entry meets the criteria to consider it an
+   * RFC 3671 LDAP collective attributes subentry (i.e., it contains
+   * the "collectiveAttributeSubentry" objectclass).
+   *
+   * @return  <CODE>true</CODE> if this entry meets the criteria to
+   *          consider it an RFC 3671 LDAP collective attributes
+   *          subentry, or <CODE>false</CODE> if not.
+   */
+  public boolean isCollectiveAttributeSubentry()
+  {
+    ObjectClass collectiveAttributeSubentryOC =
+         DirectoryServer.getObjectClass(OC_COLLECTIVE_ATTR_SUBENTRY);
+    if (collectiveAttributeSubentryOC == null)
+    {
+      // This should not happen -- The server doesn't have
+      // a collectiveAttributeSubentry objectclass defined.
+      if (debugEnabled())
+      {
+        TRACER.debugWarning(
+            "No %s objectclass is defined in the server schema.",
+                     OC_COLLECTIVE_ATTR_SUBENTRY);
+      }
+
+      for (String ocName : objectClasses.values())
+      {
+        if (ocName.equalsIgnoreCase(OC_COLLECTIVE_ATTR_SUBENTRY))
+        {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+
+    // Make the determination based on whether this entry
+    // has the collectiveAttributeSubentry objectclass.
+    return objectClasses.containsKey(collectiveAttributeSubentryOC);
+  }
+
+
+
+  /**
    * Indicates whether this entry falls within the range of the
    * provided search base DN and scope.
    *
@@ -3322,12 +3411,185 @@
 
 
   /**
+   * Performs any necessary collective attribute processing for this
+   * entry.  This should only be called at the time the entry is
+   * decoded or created within the backend.
+   */
+  private void processCollectiveAttributes()
+  {
+    if (this.isSubentry() || this.isLDAPSubentry())
+    {
+      return;
+    }
+
+    // Get applicable collective subentries.
+    List<SubEntry> collectiveAttrSubentries =
+            DirectoryServer.getSubentryManager(
+            ).getCollectiveSubentries(this);
+    if ((collectiveAttrSubentries == null) ||
+         collectiveAttrSubentries.isEmpty())
+    {
+      // Nothing to see here, move along.
+      return;
+    }
+
+    // Get collective attribute exclusions.
+    AttributeType exclusionsType = DirectoryServer.getAttributeType(
+            ATTR_COLLECTIVE_EXCLUSIONS_LC);
+    List<Attribute> exclusionsAttrList =
+            operationalAttributes.get(exclusionsType);
+    Set<String> exclusionsNameSet = new HashSet<String>();
+    if ((exclusionsAttrList != null) && !exclusionsAttrList.isEmpty())
+    {
+      for (Attribute attr : exclusionsAttrList)
+      {
+        for (AttributeValue attrValue : attr)
+        {
+          String exclusionsName = attrValue.toString().toLowerCase();
+          if (exclusionsName.equals(
+                  VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL_LC) ||
+                  exclusionsName.equals(
+                  OID_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL))
+          {
+            return;
+          }
+          exclusionsNameSet.add(exclusionsName);
+        }
+      }
+    }
+
+    // Process collective attributes.
+    for (SubEntry subEntry : collectiveAttrSubentries)
+    {
+      if (subEntry.isCollective())
+      {
+        List<Attribute> collectiveAttrList =
+                subEntry.getCollectiveAttributes();
+        for (Attribute collectiveAttr : collectiveAttrList)
+        {
+          AttributeType attributeType =
+                  collectiveAttr.getAttributeType();
+          if (exclusionsNameSet.contains(
+                  attributeType.getNormalizedPrimaryNameOrOID()))
+          {
+            continue;
+          }
+          List<Attribute> attrList =
+                  userAttributes.get(attributeType);
+          if ((attrList == null) || attrList.isEmpty())
+          {
+            attrList = operationalAttributes.get(attributeType);
+            if ((attrList == null) || attrList.isEmpty())
+            {
+              // There aren't any conflicts, so we can just add the
+              // attribute to the entry.
+              attrList = new LinkedList<Attribute>();
+              attrList.add(collectiveAttr);
+              if (attributeType.isOperational())
+              {
+                operationalAttributes.put(attributeType, attrList);
+              }
+              else
+              {
+                userAttributes.put(attributeType, attrList);
+              }
+            }
+            else
+            {
+              // There is a conflict with an existing operational
+              // attribute.
+              if (attrList.get(0).isVirtual())
+              {
+                // The existing attribute is already virtual,
+                // so we've got a different conflict, but
+                // we'll let the first win.
+                // FIXME -- Should we handle this differently?
+                continue;
+              }
+
+              // The conflict is with a real attribute.  See what the
+              // conflict behavior is and figure out how to handle it.
+              switch (subEntry.getConflictBehavior())
+              {
+                case REAL_OVERRIDES_VIRTUAL:
+                  // We don't need to update the entry because
+                  // the real attribute will take precedence.
+                  break;
+
+                case VIRTUAL_OVERRIDES_REAL:
+                  // We need to move the real attribute to the
+                  // suppressed list and replace it with the
+                  // virtual attribute.
+                  suppressedAttributes.put(attributeType, attrList);
+                  attrList = new LinkedList<Attribute>();
+                  attrList.add(collectiveAttr);
+                  operationalAttributes.put(attributeType, attrList);
+                  break;
+
+                case MERGE_REAL_AND_VIRTUAL:
+                  // We need to add the virtual attribute to the
+                  // list and keep the existing real attribute(s).
+                  attrList.add(collectiveAttr);
+                  break;
+              }
+            }
+          }
+          else
+          {
+            // There is a conflict with an existing user attribute.
+            if (attrList.get(0).isVirtual())
+            {
+              // The existing attribute is already virtual,
+              // so we've got a different conflict, but
+              // we'll let the first win.
+              // FIXME -- Should we handle this differently?
+              continue;
+            }
+
+            // The conflict is with a real attribute.  See what the
+            // conflict behavior is and figure out how to handle it.
+            switch (subEntry.getConflictBehavior())
+            {
+              case REAL_OVERRIDES_VIRTUAL:
+                // We don't need to update the entry because the real
+                // attribute will take precedence.
+                break;
+
+              case VIRTUAL_OVERRIDES_REAL:
+                // We need to move the real attribute to the
+                // suppressed list and replace it with the
+                // virtual attribute.
+                suppressedAttributes.put(attributeType, attrList);
+                attrList = new LinkedList<Attribute>();
+                attrList.add(collectiveAttr);
+                userAttributes.put(attributeType, attrList);
+                break;
+
+              case MERGE_REAL_AND_VIRTUAL:
+                // We need to add the virtual attribute to the
+                // list and keep the existing real attribute(s).
+                attrList.add(collectiveAttr);
+                break;
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+
+  /**
    * Performs any necessary virtual attribute processing for this
    * entry.  This should only be called at the time the entry is
    * decoded or created within the backend.
    */
   public void processVirtualAttributes()
   {
+    // Collective attributes.
+    processCollectiveAttributes();
+
+    // Virtual attributes.
     for (VirtualAttributeRule rule :
          DirectoryServer.getVirtualAttributes(this))
     {
diff --git a/opends/src/server/org/opends/server/types/SubEntry.java b/opends/src/server/org/opends/server/types/SubEntry.java
new file mode 100644
index 0000000..ef93ea0
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/SubEntry.java
@@ -0,0 +1,281 @@
+/*
+ * 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
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.server.types;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.LinkedHashSet;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.RFC3672SubtreeSpecification;
+
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+
+/**
+ * This class represents RFC 3672 subentries and RFC 3671
+ * collective attribute subentries objects.
+ */
+public class SubEntry {
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  /**
+   * Defines the set of permissable values for the conflict behavior.
+   * Specifies the behavior that the server is to exhibit for entries
+   * that already contain one or more real values for the associated
+   * collective attribute.
+   */
+  public static enum CollectiveConflictBehavior {
+    /**
+     * Indicates that the virtual attribute provider is to preserve
+     * any real values contained in the entry and merge them with the
+     * set of generated virtual values so that both the real and
+     * virtual values are used.
+     */
+    MERGE_REAL_AND_VIRTUAL("merge-real-and-virtual"),
+
+    /**
+     * Indicates that any real values contained in the entry are
+     * preserved and used, and virtual values are not generated.
+     */
+    REAL_OVERRIDES_VIRTUAL("real-overrides-virtual"),
+
+    /**
+     * Indicates that the virtual attribute provider suppresses any
+     * real values contained in the entry and generates virtual values
+     * and uses them.
+     */
+    VIRTUAL_OVERRIDES_REAL("virtual-overrides-real");
+
+    // String representation of the value.
+    private final String name;
+
+    /**
+     * Private constructor.
+     * @param name for this conflict behavior.
+     */
+    private CollectiveConflictBehavior(String name)
+    {
+      this.name = name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString()
+    {
+      return name;
+    }
+  }
+
+  /**
+   * The name of the "collectiveConflictBehavior" attribute type,
+   * formatted in all lowercase characters.
+   */
+  public static final String ATTR_COLLECTIVE_CONFLICT_BEHAVIOR =
+          "collectiveconflictbehavior";
+
+  // Attribute option to mark attributes collective.
+  private static final String ATTR_OPTION_COLLECTIVE =
+          "collective";
+
+  // Entry object.
+  private Entry entry;
+
+  // Subtree specification.
+  private RFC3672SubtreeSpecification subTreeSpec;
+
+  // Collective subentry flag.
+  private boolean isCollective = false;
+
+  // Collective attributes.
+  private List<Attribute> collectiveAttributes;
+
+  // Conflict behavior.
+  private CollectiveConflictBehavior conflictBehavior =
+          CollectiveConflictBehavior.REAL_OVERRIDES_VIRTUAL;
+
+  /**
+   * Constructs a subentry object from a given entry object.
+   * @param  entry LDAP subentry to construct from.
+   * @throws DirectoryException if there is a problem with
+   *         constructing a subentry from a given entry.
+   */
+  public SubEntry(Entry entry) throws DirectoryException
+  {
+    // Entry object.
+    this.entry = entry;
+
+    // Process subtree specification.
+    this.subTreeSpec = null;
+    AttributeType specAttrType = DirectoryServer.getAttributeType(
+            ATTR_SUBTREE_SPEC_LC, true);
+    List<Attribute> specAttrList =
+            entry.getAttribute(specAttrType);
+    for (Attribute attr : specAttrList)
+    {
+      for (AttributeValue value : attr)
+      {
+        this.subTreeSpec = RFC3672SubtreeSpecification.valueOf(
+                entry.getDN().getParent(), value.toString());
+        break;
+      }
+      if (this.subTreeSpec != null)
+      {
+        break;
+      }
+    }
+    // Subentry has to to have a subtree specification.
+    if (this.subTreeSpec == null)
+    {
+      // There is none for some reason so create a dummy.
+      this.subTreeSpec = new RFC3672SubtreeSpecification(
+                entry.getDN().getParent(), null, -1, -1,
+                null, null, null);
+    }
+
+    // Determine if this subentry is collective attribute subentry.
+    this.isCollective = entry.isCollectiveAttributeSubentry();
+
+    // Process collective attributes.
+    this.collectiveAttributes = new ArrayList<Attribute>();
+    if (this.isCollective)
+    {
+      List<Attribute> subAttrList = entry.getAttributes();
+      for (Attribute subAttr : subAttrList)
+      {
+        AttributeType attrType = subAttr.getAttributeType();
+        if (attrType.isCollective())
+        {
+          CollectiveVirtualAttribute collectiveAttr =
+                  new CollectiveVirtualAttribute(subAttr);
+          this.collectiveAttributes.add(collectiveAttr);
+        }
+        else if (subAttr.hasOption(ATTR_OPTION_COLLECTIVE))
+        {
+          AttributeBuilder builder = new AttributeBuilder(
+                  subAttr.getAttributeType());
+          builder.addAll(subAttr);
+          Set<String> options = new LinkedHashSet<String>(
+                  subAttr.getOptions());
+          options.remove(ATTR_OPTION_COLLECTIVE);
+          builder.setOptions(options);
+          Attribute attr = builder.toAttribute();
+          CollectiveVirtualAttribute collectiveAttr =
+                  new CollectiveVirtualAttribute(attr);
+          this.collectiveAttributes.add(collectiveAttr);
+        }
+      }
+      // Conflict behavior.
+      List<Attribute> attrList = entry.getAttribute(
+              ATTR_COLLECTIVE_CONFLICT_BEHAVIOR);
+      if ((attrList != null) && !attrList.isEmpty())
+      {
+        for (Attribute attr : attrList)
+        {
+          for (AttributeValue value : attr)
+          {
+            for (CollectiveConflictBehavior behavior :
+              CollectiveConflictBehavior.values())
+            {
+              if (behavior.toString().equals(value.toString()))
+              {
+                this.conflictBehavior = behavior;
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Retrieves the distinguished name for this subentry.
+   * @return  The distinguished name for this subentry.
+   */
+  public DN getDN()
+  {
+    return this.entry.getDN();
+  }
+
+  /**
+   * Getter to retrieve the actual entry object
+   * for this subentry.
+   * @return entry object for this subentry.
+   */
+  public Entry getEntry()
+  {
+    return this.entry;
+  }
+
+  /**
+   * Indicates whether or not this subentry is
+   * a collective attribute subentry.
+   * @return <code>true</code> if collective,
+   *         <code>false</code> otherwise.
+   */
+  public boolean isCollective()
+  {
+    return this.isCollective;
+  }
+
+  /**
+   * Getter for subentry subtree specification.
+   * @return subtree specification for this subentry.
+   */
+  public RFC3672SubtreeSpecification getSubTreeSpecification()
+  {
+    return this.subTreeSpec;
+  }
+
+  /**
+   * Getter for collective attributes contained within this subentry.
+   * @return collective attributes contained within this subentry.
+   */
+  public List<Attribute> getCollectiveAttributes()
+  {
+    return this.collectiveAttributes;
+  }
+
+  /**
+   * Getter for collective conflict behavior defined for this
+   * collective attributes subentry.
+   * @return conflict behavior for this collective attributes
+   *         subentry.
+   */
+  public CollectiveConflictBehavior getConflictBehavior()
+  {
+    return this.conflictBehavior;
+  }
+}
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index b80e4c1..235bb32 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -224,6 +224,69 @@
 
 
   /**
+   * The name of the standard "subtreeSpecification" attribute type,
+   * formatted in camel case.
+   */
+  public static final String ATTR_SUBTREE_SPEC = "subtreeSpecification";
+
+
+
+  /**
+   * The name of the standard "subtreeSpecification" attribute type,
+   * formatted in all lowercase characters.
+   */
+  public static final String ATTR_SUBTREE_SPEC_LC = "subtreespecification";
+
+
+
+  /**
+   * The name of the standard "collectiveExclusions" attribute type,
+   * formatted in camel case.
+   */
+  public static final String ATTR_COLLECTIVE_EXCLUSIONS =
+          "collectiveExclusions";
+
+
+
+  /**
+   * The name of the standard "collectiveExclusions" attribute type,
+   * formatted in all lowercase characters.
+   */
+  public static final String ATTR_COLLECTIVE_EXCLUSIONS_LC =
+          "collectiveexclusions";
+
+
+
+  /**
+   * The value of the standard "excludeAllCollectiveAttributes" attribute
+   * value of the standard "collectiveExclusions" attribute type,
+   * formatted in camel case.
+   */
+  public static final String VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL =
+          "excludeAllCollectiveAttributes";
+
+
+
+  /**
+   * The value of the standard "excludeAllCollectiveAttributes" attribute
+   * value of the standard "collectiveExclusions" attribute type,
+   * formatted in all lowercase characters.
+   */
+  public static final String VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL_LC =
+          "excludeallcollectiveattributes";
+
+
+
+  /**
+   * The OID of the standard "excludeAllCollectiveAttributes" attribute
+   * value of the standard "collectiveExclusions" attribute type.
+   */
+  public static final String OID_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL =
+          "2.5.18.0";
+
+
+
+  /**
    * The name of the monitor attribute that is used to hold a backend ID.
    */
   public static final String ATTR_MONITOR_BACKEND_ID = "ds-backend-id";
@@ -837,6 +900,35 @@
 
 
   /**
+   * The name of the RFC 3672 "subentry" objectclass (which is a special
+   * type of objectclass that makes a kind of "operational" entry),
+   * formatted in all lowercase.
+   */
+  public static final String OC_SUBENTRY = "subentry";
+
+
+
+  /**
+   * The name of the RFC 3671 "collectiveAttributeSubentry" objectclass
+   * (which is a special type of objectclass that makes a kind of shared
+   * attributes subentry), formatted in camel case.
+   */
+  public static final String OC_COLLECTIVE_ATTR_SUBENTRY =
+          "collectiveAttributeSubentry";
+
+
+
+  /**
+   * The name of the RFC 3671 "collectiveAttributeSubentry" objectclass
+   * (which is a special type of objectclass that makes a kind of shared
+   * attributes subentry), formatted in all lowercase.
+   */
+  public static final String OC_COLLECTIVE_ATTR_SUBENTRY_LC =
+          "collectiveattributesubentry";
+
+
+
+  /**
    * The name of the custom objectclass that will be included in backend monitor
    * entries.
    */
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AttributeTypeSyntaxTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AttributeTypeSyntaxTest.java
index 9f9e23a..425b3c8 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AttributeTypeSyntaxTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AttributeTypeSyntaxTest.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Copyright 2006-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.schema;
 
@@ -81,13 +81,13 @@
           " SUBSTR caseIgnoreSubstringsMatch" +
           " SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE" +
           " COLLECTIVE USAGE userApplications )",
-          false}, // Collective can't inherit from non-collective
+          true}, // Collective can inherit from non-collective
         {"(1.2.8.5 NAME 'testtype' DESC 'full type' OBSOLETE " +
           " EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch" +
           " SUBSTR caseIgnoreSubstringsMatch" +
           " SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE" +
           " COLLECTIVE USAGE directoryOperation )",
-          false}, // Collective can't be operational
+          true}, // Collective can be operational
         {"(1.2.8.5 NAME 'testtype' DESC 'full type' OBSOLETE SUP cn " +
           " EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch" +
           " SUBSTR caseIgnoreSubstringsMatch" +

--
Gitblit v1.10.0