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

abobrov
25.28.2009 6912362c6715306768c8fc14dd18ad9293e4c2ca
- land Subentry Manager and Collective Attributes implementations; Merry XMAS to yall!
5 files added
12 files modified
1775 ■■■■■ changed files
opends/resource/config/config.ldif 10 ●●●●● patch | view | raw | blame | history
opends/resource/schema/00-core.ldif 43 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 5 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/CollectiveAttributeSubentriesVirtualAttributeConfiguration.xml 70 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/core.properties 3 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/extension.properties 3 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/schema.properties 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SearchOperationBasis.java 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SubentryManager.java 647 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/CollectiveAttributeSubentriesVirtualAttributeProvider.java 153 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java 31 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/CollectiveVirtualAttribute.java 131 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Entry.java 264 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/SubEntry.java 281 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 92 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/AttributeTypeSyntaxTest.java 6 ●●●● patch | view | raw | blame | history
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
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 )
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' )
opends/src/admin/defn/org/opends/server/admin/std/CollectiveAttributeSubentriesVirtualAttributeConfiguration.xml
New file
@@ -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>
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
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
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
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
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);
        }
opends/src/server/org/opends/server/core/SubentryManager.java
New file
@@ -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.
      }
    }
  }
}
opends/src/server/org/opends/server/extensions/CollectiveAttributeSubentriesVirtualAttributeProvider.java
New file
@@ -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);
  }
}
opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java
@@ -983,32 +983,17 @@
        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
      }
      if (superiorType.isCollective() != isCollective)
      if (superiorType.isCollective())
      {
        Message message;
        if (isCollective)
        {
          message = WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_FROM_NONCOLLECTIVE.get(
                  oid, superiorType.getNameOrOID());
        }
        else
        {
          message =
                  WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE.get(
                    oid, superiorType.getNameOrOID());
        }
        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))
        if (!isCollective)
    {
      Message message =
          WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_IS_OPERATIONAL.get(oid);
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
                  WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE.get(
                    oid, superiorType.getNameOrOID());
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
                  message);
        }
      }
    }
opends/src/server/org/opends/server/types/CollectiveVirtualAttribute.java
New file
@@ -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);
  }
}
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))
    {
opends/src/server/org/opends/server/types/SubEntry.java
New file
@@ -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;
  }
}
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.
   */
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" +