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

neil_a_wilson
03.52.2007 e1ea3e0d8999105f144d2be98e0286928b8319ed
Add initial support for a virtual attribute subsystem, and implement a few
different kinds of virtual attributes. This commit addresses the following
issues:

- Issue #1475 -- General virtual attribute support
- Issue #539 -- Support for the isMemberOf virtual attribute
- Issue #544 -- Support for the entryDN virtual attribute
- Issue #1056 -- Support for the subschemaSubentry virtual attribute
- Issue #85 -- Support for the real attributes only control
- Issue #86 -- Support for the virutal attributes only control

In general, virtual attribute support consists of three parts:

- An implementation of the org.opends.server.api.VirtualAttributeProvider
class, which provides the logic for actually generating the values, providing
support for various kinds of matching, and potentially the ability to process
search operations involving the virtual attribute that might not otherwise be
indexed.

- The org.opends.server.types.VirtualAttribute class, which is a subclass of
org.opends.server.types.Attribute and uses the virtual attribute provider to
generate its values.

- The org.opends.server.types.VirtualAttributeRule class, which associates a
virtual attribute provider with a given attribute type, and also with a set
of criteria that controls which entries should have the attribute.


The virtual attribute rule currently supports the following criteria that can
be used to decide whether an entry should have a given virtual attribute:

- Zero or more base DNs. If any base DNs are provided, then any entry which
falls below one of those base DNs will be a candidate to get the virtual
attribute. If no base DNs are provided, then DIT location will not be taken
into account when determining eligibility.

- Zero or more group DNs. If any group DNs are provided, then any entry that
belongs to one of the specified groups will be a candidate to get the virtual
attribute. If no group DNs are provided, then group membership will not be
taken into account when determining eligibility.

- Zero or more search filters. If any filters are provided, then any entry
that matches one of the specified filters will be a candidate to get the
virtual attribute. If no filters are provided, then the contents of the
entry will not be taken into account when determining eligibility.


In addition to that criteria, virtual attribute rules define a conflict
behavior, which controls how to behave when the entry already has one or more
real values for the attribute. The conflict behavior can be
"real-overrides-virtual" (to only show the real values),
"virtual-overrides-real" (to only show the virtual values), or
"merge-real-and-virtual" (to show both real and virtual values).

The virtual attribute implementation has been designed so that there should be
virtually no performance impact unless the attribute needs to be returned to
the client or it is referenced in a search filter, and you can completely
disable virtual attributes if you don't need them.
1 files deleted
13 files added
40 files modified
8249 ■■■■■ changed files
opends/resource/config/config.ldif 28 ●●●●● patch | view | raw | blame | history
opends/resource/schema/00-core.ldif 4 ●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 28 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml 8 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/VirtualAttributeConfiguration.xml 183 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ClientConnection.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/Group.java 3 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/VirtualAttribute.java 141 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/VirtualAttributeProvider.java 637 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/BackupBackend.java 15 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MemoryBackend.java 31 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MonitorBackend.java 13 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/RootDSEBackend.java 33 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java 5 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/JebFormat.java 8 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskBackend.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskScheduler.java 14 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigEntry.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperation.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DeleteOperation.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 125 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyDNOperation.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperation.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SearchOperation.java 32 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/VirtualAttributeConfigManager.java 578 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java 399 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java 437 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java 210 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/interop/LazyDN.java 23 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ConfigMessages.java 63 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 15 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java 50 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/LDAPToolUtils.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Attribute.java 54 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DN.java 58 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Entry.java 430 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/LDIFExportConfig.java 38 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/VirtualAttribute.java 258 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/VirtualAttributeRule.java 408 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/LDIFWriter.java 2 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 15 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java 1116 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java 1277 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProviderTestCase.java 848 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/interop/LazyDNTestCase.java 39 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/ProtocolWindowTest.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeRuleTestCase.java 337 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeTestCase.java 192 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1669,6 +1669,34 @@
objectClass: ds-cfg-branch
cn: Virtual Attributes
dn: cn=entryDN,cn=Virtual Attributes,cn=config
objectClass: top
objectClass: ds-cfg-virtual-attribute
cn: entryDN
ds-cfg-virtual-attribute-class: org.opends.server.extensions.EntryDNVirtualAttributeProvider
ds-cfg-virtual-attribute-enabled: true
ds-cfg-virtual-attribute-type: entryDN
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
dn: cn=isMemberOf,cn=Virtual Attributes,cn=config
objectClass: top
objectClass: ds-cfg-virtual-attribute
cn: isMemberOf
ds-cfg-virtual-attribute-class: org.opends.server.extensions.IsMemberOfVirtualAttributeProvider
ds-cfg-virtual-attribute-enabled: true
ds-cfg-virtual-attribute-type: isMemberOf
ds-cfg-virtual-attribute-filter: (objectClass=person)
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
dn: cn=subschemaSubentry,cn=Virtual Attributes,cn=config
objectClass: top
objectClass: ds-cfg-virtual-attribute
cn: subschemaSubentry
ds-cfg-virtual-attribute-class: org.opends.server.extensions.SubschemaSubentryVirtualAttributeProvider
ds-cfg-virtual-attribute-enabled: true
ds-cfg-virtual-attribute-type: subschemaSubentry
ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real
dn: cn=Work Queue,cn=config
objectClass: top
objectClass: ds-cfg-work-queue
opends/resource/schema/00-core.ldif
@@ -420,6 +420,10 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'RFC 1274' )
attributeTypes: ( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'RFC 1274' )
attributeTypes: ( 2.16.840.1.113730.3.1.602 NAME 'entryDN'
  DESC 'DN of the entry' EQUALITY distinguishedNameMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION
  USAGE directoryOperation X-ORIGIN 'draft-zeilenga-ldap-entrydn' )
objectClasses: ( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass
  X-ORIGIN 'RFC 4512' )
objectClasses: ( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName
opends/resource/schema/02-config.ldif
@@ -1103,6 +1103,28 @@
  NAME 'ds-cfg-case-sensitive-validation' 
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE 
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.325
  NAME 'ds-cfg-virtual-attribute-class' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.326
  NAME 'ds-cfg-virtual-attribute-enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.327
  NAME 'ds-cfg-virtual-attribute-type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.328
  NAME 'ds-cfg-virtual-attribute-base-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.329
  NAME 'ds-cfg-virtual-attribute-group-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.330
  NAME 'ds-cfg-virtual-attribute-filter' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.331
  NAME 'ds-cfg-virtual-attribute-conflict-behavior'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1532,4 +1554,10 @@
  SUP ds-cfg-password-validator STRUCTURAL
  MUST ( ds-cfg-maximum-consecutive-length $ ds-cfg-case-sensitive-validation )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.91 NAME 'ds-cfg-virtual-attribute'
  SUP top STRUCTURAL MUST ( cn $ ds-cfg-virtual-attribute-class $
  ds-cfg-virtual-attribute-enabled $ ds-cfg-virtual-attribute-type $
  ds-cfg-virtual-attribute-conflict-behavior )
  MAY ( ds-cfg-virtual-attribute-base-dn $ ds-cfg-virtual-attribute-group-dn $
  ds-cfg-virtual-attribute-filter ) X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml
@@ -61,5 +61,13 @@
      </ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:relation name="virtual-attribute">
    <adm:one-to-many />
    <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Virtual Attributes,cn=config
      </ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:product-name>OpenDS Directory Server</adm:product-name>
</adm:root-managed-object>
opends/src/admin/defn/org/opends/server/admin/std/VirtualAttributeConfiguration.xml
New file
@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<adm:managed-object name="virtual-attribute"
plural-name="virtual-attributes"
package="org.opends.server.admin.std"
xmlns:adm="http://www.opends.org/admin"
xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    <adm:user-friendly-plural-name />
    are responsible for dynamically generating attribute values that appear in
    entries but are not persistently stored in the backend.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.91</ldap:oid>
      <ldap:name>ds-cfg-virtual-attribute</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="provider-class" mandatory="true">
    <adm:synopsis>
      The fully-qualified name of the Java class that provides the
      <adm:user-friendly-name />
      implementation.
    </adm:synopsis>
    <adm:syntax>
      <adm:java-class>
        <adm:instance-of>
          org.opends.server.api.VirtualAttributeProvider
        </adm:instance-of>
      </adm:java-class>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.325</ldap:oid>
        <ldap:name>ds-cfg-virtual-attribute-class</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="enabled" mandatory="true">
    <adm:synopsis>
      Indicate whether the
      <adm:user-friendly-name />
      is enabled for use.
    </adm:synopsis>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.326</ldap:oid>
        <ldap:name>ds-cfg-virtual-attribute-enabled</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="attribute-type" mandatory="true">
    <adm:synopsis>
      Specifies the attribute type for the attribute whose values should be
      dynamically assigned by the virtual attribute.
    </adm:synopsis>
    <adm:syntax>
      <adm:attribute-type />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.327</ldap:oid>
        <ldap:name>ds-cfg-virtual-attribute-type</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="base-dn" mandatory="false" multi-valued="true">
    <adm:synopsis>
      Specifies the base DNs for the branches containing entries that may be
      eligible to use this virtual attribute.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          The location of the entry in the server will not be taken into account
          when determining whether an entry is eligible to use this virtual
          attribute.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:dn />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.328</ldap:oid>
        <ldap:name>ds-cfg-virtual-attribute-base-dn</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="group-dn" mandatory="false" multi-valued="true">
    <adm:synopsis>
      Specifies the DNs for the groups whose members may be eligible to use this
      virtual attribute.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          Group membership will not be taken into accountwhen determining
          whether an entry is eligible to use this virtual attribute.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:dn />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.329</ldap:oid>
        <ldap:name>ds-cfg-virtual-attribute-group-dn</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="filter" mandatory="false" multi-valued="true">
    <adm:synopsis>
      Specifies the search filters for entries that may be eligible to use this
      virtual attribute.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>(objectClass=*)</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.330</ldap:oid>
        <ldap:name>ds-cfg-virtual-attribute-filter</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="conflict-behavior" mandatory="false">
    <adm:synopsis>
      Specifies the behavior that the server should exhibit for entries that
      contain one or more real values for the associated attribute.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>real-overrides-virtual</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="real-overrides-virtual">
          <adm:synopsis>
            Any real values contained in the entry should be preserved and
            virtual values should not be generated.
          </adm:synopsis>
        </adm:value>
        <adm:value name="virtual-overrides-real">
          <adm:synopsis>
            Any real values contained in the entry should be suppressed and
            virtual values should be generated.
          </adm:synopsis>
        </adm:value>
        <adm:value name="merge-real-and-virtual">
          <adm:synopsis>
            Any real values contained in the entry should be preserved and
            merged with the set of generated virtual values.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.331</ldap:oid>
        <ldap:name>ds-cfg-virtual-attribute-conflict-behavior</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/server/org/opends/server/api/ClientConnection.java
@@ -73,9 +73,6 @@
 */
public abstract class ClientConnection
{
  // The set of authentication information for this client connection.
  private AuthenticationInfo authenticationInfo;
@@ -1197,7 +1194,7 @@
   *                    searches performed using this client
   *                    connection.
   */
  public final void setSizeLimit(int sizeLimit)
  public void setSizeLimit(int sizeLimit)
  {
    this.sizeLimit = sizeLimit;
  }
@@ -1226,7 +1223,7 @@
   *                           entries that should be check for
   *                           matches during a search.
   */
  public final void setLookthroughLimit(int lookthroughLimit)
  public void setLookthroughLimit(int lookthroughLimit)
  {
    this.lookthroughLimit = lookthroughLimit;
  }
@@ -1255,7 +1252,7 @@
   *                    searches performed using this client
   *                    connection.
   */
  public final void setTimeLimit(int timeLimit)
  public void setTimeLimit(int timeLimit)
  {
    this.timeLimit = timeLimit;
  }
opends/src/server/org/opends/server/api/Group.java
@@ -63,9 +63,6 @@
 */
public abstract class Group
{
  /**
   * Initializes a "shell" instance of this group implementation that
   * may be used to identify and instantiate instances of this type of
opends/src/server/org/opends/server/api/VirtualAttribute.java
File was deleted
opends/src/server/org/opends/server/api/VirtualAttributeProvider.java
New file
@@ -0,0 +1,637 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.api;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.VirtualAttributeRule;
import static org.opends.server.loggers.debug.DebugLogger.*;
/**
 * This class defines the set of methods and structures that must be
 * implemented by a Directory Server module that implements the
 * functionality required for one or more virtual attributes.
 *
 * @param  <T>  The type of configuration handled by this virtual
 *              attribute provider.
 */
public abstract class VirtualAttributeProvider
       <T extends VirtualAttributeCfg>
{
  /**
   * Initializes this virtual attribute based on the information in
   * the provided configuration entry.
   *
   * @param  configuration  The configuration to use to initialize
   *                        this virtual attribute provider.
   *
   * @throws  ConfigException  If an unrecoverable problem arises in
   *                           the process of performing the
   *                           initialization.
   *
   * @throws  InitializationException  If a problem occurs during
   *                                   initialization that is not
   *                                   related to the server
   *                                   configuration.
   */
  public abstract void initializeVirtualAttributeProvider(
                            T configuration)
         throws ConfigException, InitializationException;
  /**
   * Performs any finalization that may be necessary whenever this
   * virtual attribute provider is taken out of service.
   */
  public void finalizeVirtualAttributeProvider()
  {
    // No implementation required by default.
  }
  /**
   * Indicates whether this virtual attribute provider may generate
   * multiple values.
   *
   * @return  {@code true} if this virtual attribute provider may
   *          generate multiple values, or {@code false} if not.
   */
  public abstract boolean isMultiValued();
  /**
   * Generates a set of values for the provided entry.
   *
   * @param  entry  The entry for which the values are to be
   *                generated.
   * @param  rule   The virtual attribute rule which defines the
   *                constraints for the virtual attribute.
   *
   * @return  The set of values generated for the provided entry.  It
   *          may be empty, but it must not be {@code null}.
   */
  public abstract LinkedHashSet<AttributeValue>
                       getValues(Entry entry,
                                 VirtualAttributeRule rule);
  /**
   * Indicates whether this virtual attribute provider will generate
   * at least one value for the provided entry.
   *
   * @param  entry  The entry for which to make the determination.
   * @param  rule   The virtual attribute rule which defines the
   *                constraints for the virtual attribute.
   *
   * @return  {@code true} if this virtual attribute provider will
   *          generate at least one value for the provided entry, or
   *          {@code false} if not.
   */
  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
  {
    return (! getValues(entry, rule).isEmpty());
  }
  /**
   * Indicates whether this virtual attribute provider will generate
   * the provided value.
   *
   * @param  entry  The entry for which to make the determination.
   * @param  rule   The virtual attribute rule which defines the
   *                constraints for the virtual attribute.
   * @param  value  The value for which to make the determination.
   *
   * @return  {@code true} if this virtual attribute provider will
   *          generate the specified vaule for the provided entry, or
   *          {@code false} if not.
   */
  public boolean hasValue(Entry entry, VirtualAttributeRule rule,
                          AttributeValue value)
  {
    return getValues(entry, rule).contains(value);
  }
  /**
   * Indicates whether this virtual attribute provider will generate
   * all of the values in the provided collection.
   *
   * @param  entry   The entry for which to make the determination.
   * @param  rule    The virtual attribute rule which defines the
   *                 constraints for the virtual attribute.
   * @param  values  The set of values for which to make the
   *                 determination.
   *
   * @return  {@code true} if this attribute provider will generate
   *          all of the values in the provided collection, or
   *          {@code false} if it will not generate at least one of
   *          them.
   */
  public boolean hasAllValues(Entry entry, VirtualAttributeRule rule,
                              Collection<AttributeValue> values)
  {
    for (AttributeValue value : values)
    {
      if (! getValues(entry, rule).contains(value))
      {
        return false;
      }
    }
    return true;
  }
  /**
   * Indicates whether this virutal attribute provider will generate
   * any of the values in the provided collection.
   *
   * @param  entry   The entry for which to make the determination.
   * @param  rule    The virtual attribute rule which defines the
   *                 constraints for the virtual attribute.
   * @param  values  The set of values for which to make the
   *                 determination.
   *
   * @return  {@code true} if this attribute provider will generate
   *          at least one of the values in the provided collection,
   *          or {@code false} if it will not generate any of them.
   */
  public boolean hasAnyValue(Entry entry, VirtualAttributeRule rule,
                             Collection<AttributeValue> values)
  {
    for (AttributeValue value : values)
    {
      if (getValues(entry, rule).contains(value))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * Indicates whether this virtual attribute provider will generate
   * any value which matches the provided substring.
   *
   * @param  entry       The entry for which to make the
   *                     determination.
   * @param  rule        The virtual attribute rule which defines the
   *                     constraints for the virtual attribute.
   * @param  subInitial  The subInitial component to use in the
   *                     determination.
   * @param  subAny      The subAny components to use in the
   *                     determination.
   * @param  subFinal    The subFinal component to use in the
   *                     determination.
   *
   * @return  <CODE>UNDEFINED</CODE> if this attribute does not have a
   *          substring matching rule, <CODE>TRUE</CODE> if at least
   *          one value matches the provided substring, or
   *          <CODE>FALSE</CODE> otherwise.
   */
  public ConditionResult matchesSubstring(Entry entry,
                                          VirtualAttributeRule rule,
                                          ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    SubstringMatchingRule matchingRule =
         rule.getAttributeType().getSubstringMatchingRule();
    if (matchingRule == null)
    {
      return ConditionResult.UNDEFINED;
    }
    ByteString normalizedSubInitial;
    if (subInitial == null)
    {
      normalizedSubInitial = null;
    }
    else
    {
      try
      {
        normalizedSubInitial =
             matchingRule.normalizeSubstring(subInitial);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        // The substring couldn't be normalized.  We have to return
        // "undefined".
        return ConditionResult.UNDEFINED;
      }
    }
    ArrayList<ByteString> normalizedSubAny;
    if (subAny == null)
    {
      normalizedSubAny = null;
    }
    else
    {
      normalizedSubAny =
           new ArrayList<ByteString>(subAny.size());
      for (ByteString subAnyElement : subAny)
      {
        try
        {
          normalizedSubAny.add(matchingRule.normalizeSubstring(
                                                 subAnyElement));
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            debugCaught(DebugLogLevel.ERROR, e);
          }
          // The substring couldn't be normalized.  We have to return
          // "undefined".
          return ConditionResult.UNDEFINED;
        }
      }
    }
    ByteString normalizedSubFinal;
    if (subFinal == null)
    {
      normalizedSubFinal = null;
    }
    else
    {
      try
      {
        normalizedSubFinal =
             matchingRule.normalizeSubstring(subFinal);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        // The substring couldn't be normalized.  We have to return
        // "undefined".
        return ConditionResult.UNDEFINED;
      }
    }
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue value : getValues(entry, rule))
    {
      try
      {
        if (matchingRule.valueMatchesSubstring(
                              value.getNormalizedValue(),
                              normalizedSubInitial,
                              normalizedSubAny,
                              normalizedSubFinal))
        {
          return ConditionResult.TRUE;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        // The value couldn't be normalized.  If we can't find a
        // definite match, then we should return "undefined".
        result = ConditionResult.UNDEFINED;
      }
    }
    return result;
  }
  /**
   * Indicates whether this virtual attribute provider will generate
   * any value for the provided entry that is greater than or equal to
   * the given value.
   *
   * @param  entry  The entry for which to make the determination.
   * @param  rule   The virtual attribute rule which defines the
   *                constraints for the virtual attribute.
   * @param  value  The value for which to make the determination.
   *
   * @return  {@code UNDEFINED} if the associated attribute type does
   *          not have an ordering matching rule, {@code TRUE} if at
   *          least one of the generated values will be greater than
   *          or equal to the specified value, or {@code FALSE} if
   *          none of the generated values will be greater than or
   *          equal to the specified value.
   */
  public ConditionResult greaterThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    OrderingMatchingRule matchingRule =
         rule.getAttributeType().getOrderingMatchingRule();
    if (matchingRule == null)
    {
      return ConditionResult.UNDEFINED;
    }
    ByteString normalizedValue;
    try
    {
      normalizedValue = value.getNormalizedValue();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // We couldn't normalize the provided value.  We should return
      // "undefined".
      return ConditionResult.UNDEFINED;
    }
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue v : getValues(entry, rule))
    {
      try
      {
        ByteString nv = v.getNormalizedValue();
        int comparisonResult =
                 matchingRule.compareValues(nv, normalizedValue);
        if (comparisonResult >= 0)
        {
          return ConditionResult.TRUE;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        // We couldn't normalize one of the attribute values.  If we
        // can't find a definite match, then we should return
        // "undefined".
        result = ConditionResult.UNDEFINED;
      }
    }
    return result;
  }
  /**
   * Indicates whether this virtual attribute provider will generate
   * any value for the provided entry that is less than or equal to
   * the given value.
   *
   * @param  entry  The entry for which to make the determination.
   * @param  rule   The virtual attribute rule which defines the
   *                constraints for the virtual attribute.
   * @param  value  The value for which to make the determination.
   *
   * @return  {@code UNDEFINED} if the associated attribute type does
   *          not have an ordering matching rule, {@code TRUE} if at
   *          least one of the generated values will be less than or
   *          equal to the specified value, or {@code FALSE} if none
   *          of the generated values will be greater than or equal to
   *          the specified value.
   */
  public ConditionResult lessThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    OrderingMatchingRule matchingRule =
         rule.getAttributeType().getOrderingMatchingRule();
    if (matchingRule == null)
    {
      return ConditionResult.UNDEFINED;
    }
    ByteString normalizedValue;
    try
    {
      normalizedValue = value.getNormalizedValue();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // We couldn't normalize the provided value.  We should return
      // "undefined".
      return ConditionResult.UNDEFINED;
    }
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue v : getValues(entry, rule))
    {
      try
      {
        ByteString nv = v.getNormalizedValue();
        int comparisonResult =
                 matchingRule.compareValues(nv, normalizedValue);
        if (comparisonResult <= 0)
        {
          return ConditionResult.TRUE;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        // We couldn't normalize one of the attribute values.  If we
        // can't find a definite match, then we should return
        // "undefined".
        result = ConditionResult.UNDEFINED;
      }
    }
    return result;
  }
  /**
   * Indicates whether this virtual attribute provider will generate
   * any value for the provided entry that is approximately equal to
   * the given value.
   *
   * @param  entry  The entry for which to make the determination.
   * @param  rule   The virtual attribute rule which defines the
   *                constraints for the virtual attribute.
   * @param  value  The value for which to make the determination.
   *
   * @return  {@code UNDEFINED} if the associated attribute type does
   *          not have an aproximate matching rule, {@code TRUE} if at
   *          least one of the generated values will be approximately
   *          equal to the specified value, or {@code FALSE} if none
   *          of the generated values will be approximately equal to
   *          the specified value.
   */
  public ConditionResult approximatelyEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    ApproximateMatchingRule matchingRule =
         rule.getAttributeType().getApproximateMatchingRule();
    if (matchingRule == null)
    {
      return ConditionResult.UNDEFINED;
    }
    ByteString normalizedValue;
    try
    {
      normalizedValue = matchingRule.normalizeValue(value.getValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      // We couldn't normalize the provided value.  We should return
      // "undefined".
      return ConditionResult.UNDEFINED;
    }
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue v : getValues(entry, rule))
    {
      try
      {
        ByteString nv = matchingRule.normalizeValue(v.getValue());
        if (matchingRule.approximatelyMatch(nv, normalizedValue))
        {
          return ConditionResult.TRUE;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        // We couldn't normalize one of the attribute values.  If we
        // can't find a definite match, then we should return
        // "undefined".
        result = ConditionResult.UNDEFINED;
      }
    }
    return result;
  }
  /**
   * Indicates whether this attribute may be included in search
   * filters as part of the criteria for locating entries.
   *
   * @param  rule             The virtual attribute rule which defines
   *                          the constraints for the virtual
   *                          attribute.
   * @param  searchOperation  The search operation for which to make
   *                          the determination.
   *
   * @return  <CODE>true</CODE> if this attribute may be included in
   *          search filters, or <CODE>false</CODE> if not.
   */
  public abstract boolean isSearchable(VirtualAttributeRule rule,
                                       SearchOperation
                                            searchOperation);
  /**
   * Processes the provided search operation in which the search
   * criteria includes an operation targeted at this virtual
   * attribute.  This method should only be called if
   * <CODE>isSearchable</CODE> returns true and it is not possible to
   * construct a manageable candidate list by processing other
   * elements of the search criteria.
   *
   * @param  rule             The virtual attribute rule which defines
   *                          the constraints for the virtual
   *                          attribute.
   * @param  searchOperation  The search operation to be processed.
   */
  public abstract void processSearch(VirtualAttributeRule rule,
                                     SearchOperation searchOperation);
}
opends/src/server/org/opends/server/backends/BackupBackend.java
@@ -97,9 +97,6 @@
       extends Backend
       implements ConfigurableComponent
{
  // The DN of the configuration entry for this backend.
  private DN configEntryDN;
@@ -132,8 +129,6 @@
  {
    super();
    // Perform all initialization in initializeBackend.
  }
@@ -413,7 +408,7 @@
    // If the requested entry was the backend base entry, then retrieve it.
    if (entryDN.equals(backupBaseDN))
    {
      return backupBaseEntry;
      return backupBaseEntry.duplicate(true);
    }
@@ -543,7 +538,9 @@
    userAttrs.put(t, attrList);
    return new Entry(entryDN, ocMap, userAttrs, opAttrs);
    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
    e.processVirtualAttributes();
    return e;
  }
@@ -764,7 +761,9 @@
    }
    return new Entry(entryDN, ocMap, userAttrs, opAttrs);
    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
    e.processVirtualAttributes();
    return e;
  }
opends/src/server/org/opends/server/backends/MemoryBackend.java
@@ -102,9 +102,6 @@
public class MemoryBackend
       extends Backend
{
  // The base DNs for this backend.
  private DN[] baseDNs;
@@ -134,8 +131,6 @@
  {
    super();
    // Perform all initialization in initializeBackend.
  }
@@ -297,8 +292,10 @@
  public synchronized void addEntry(Entry entry, AddOperation addOperation)
         throws DirectoryException
  {
    Entry e = entry.duplicate(true);
    // See if the target entry already exists.  If so, then fail.
    DN entryDN = entry.getDN();
    DN entryDN = e.getDN();
    if (entryMap.containsKey(entryDN))
    {
      int    msgID   = MSGID_MEMORYBACKEND_ENTRY_ALREADY_EXISTS;
@@ -311,7 +308,7 @@
    // If the entry is one of the base DNs, then add it.
    if (baseDNSet.contains(entryDN))
    {
      entryMap.put(entryDN, entry);
      entryMap.put(entryDN, e);
      return;
    }
@@ -332,7 +329,7 @@
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, msgID);
    }
    entryMap.put(entryDN, entry);
    entryMap.put(entryDN, e);
    HashSet<DN> children = childDNs.get(parentDN);
    if (children == null)
    {
@@ -441,8 +438,10 @@
                                        ModifyOperation modifyOperation)
         throws DirectoryException
  {
    Entry e = entry.duplicate(true);
    // Make sure the entry exists.  If not, then throw an exception.
    DN entryDN = entry.getDN();
    DN entryDN = e.getDN();
    if (! entryMap.containsKey(entryDN))
    {
      int    msgID   = MSGID_MEMORYBACKEND_ENTRY_DOESNT_EXIST;
@@ -452,7 +451,7 @@
    // Replace the old entry with the new one.
    entryMap.put(entryDN, entry);
    entryMap.put(entryDN, e);
  }
@@ -464,6 +463,8 @@
                                       ModifyDNOperation modifyDNOperation)
         throws DirectoryException
  {
    Entry e = entry.duplicate(true);
    // Make sure that the target entry exists.
    if (! entryMap.containsKey(currentDN))
    {
@@ -492,10 +493,10 @@
    // Make sure that no entry exists with the new DN.
    if (entryMap.containsKey(entry.getDN()))
    if (entryMap.containsKey(e.getDN()))
    {
      int    msgID   = MSGID_MEMORYBACKEND_ENTRY_ALREADY_EXISTS;
      String message = getMessage(msgID, String.valueOf(entry.getDN()));
      String message = getMessage(msgID, String.valueOf(e.getDN()));
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, msgID);
    }
@@ -504,7 +505,7 @@
    boolean matchFound = false;
    for (DN dn : baseDNs)
    {
      if (dn.isAncestorOf(entry.getDN()))
      if (dn.isAncestorOf(e.getDN()))
      {
        matchFound = true;
        break;
@@ -521,7 +522,7 @@
    // Make sure that the parent of the new entry exists.
    DN parentDN = entry.getDN().getParentDNInSuffix();
    DN parentDN = e.getDN().getParentDNInSuffix();
    if ((parentDN == null) || (! entryMap.containsKey(parentDN)))
    {
      int    msgID   = MSGID_MEMORYBACKEND_RENAME_PARENT_DOESNT_EXIST;
@@ -534,7 +535,7 @@
    // Delete the current entry and add the new one.
    deleteEntry(currentDN, null);
    addEntry(entry, null);
    addEntry(e, null);
  }
opends/src/server/org/opends/server/backends/MonitorBackend.java
@@ -92,9 +92,6 @@
       extends Backend
       implements ConfigurableComponent
{
  // The set of user-defined attributes that will be included in the base
  // monitor entry.
  private ArrayList<Attribute> userDefinedAttributes;
@@ -128,8 +125,6 @@
  {
    super();
    // Perform all initialization in initializeBackend.
  }
@@ -640,8 +635,10 @@
    // Construct and return the entry.
    return new Entry(baseMonitorDN, monitorClasses, monitorUserAttrs,
    Entry e = new Entry(baseMonitorDN, monitorClasses, monitorUserAttrs,
                     monitorOperationalAttrs);
    e.processVirtualAttributes();
    return e;
  }
@@ -706,8 +703,10 @@
      }
    }
    return new Entry(entryDN, monitorClasses, attrMap,
    Entry e = new Entry(entryDN, monitorClasses, attrMap,
                     new HashMap<AttributeType,List<Attribute>>(0));
    e.processVirtualAttributes();
    return e;
  }
opends/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -105,9 +105,6 @@
       extends Backend
       implements ConfigurableComponent
{
  // The set of standard "static" attributes that we will always include in the
  // root DSE entry and won't change while the server is running.
  private ArrayList<Attribute> staticDSEAttributes;
@@ -153,8 +150,6 @@
  {
    super();
    // Perform all initialization in initializeBackend.
  }
@@ -644,30 +639,6 @@
    }
    // Add the "subschemaSubentry" attribute.
    DN schemaDN = DirectoryServer.getSchemaDN();
    if (schemaDN != null)
    {
      Attribute subschemaSubentryAttr =
           createAttribute(ATTR_SUBSCHEMA_SUBENTRY, ATTR_SUBSCHEMA_SUBENTRY_LC,
                           String.valueOf(schemaDN));
      ArrayList<Attribute> subschemaSubentryAttrs = new ArrayList<Attribute>(1);
      subschemaSubentryAttrs.add(subschemaSubentryAttr);
      if (showAllAttributes ||
          (! subschemaSubentryAttr.getAttributeType().isOperational()))
      {
        dseUserAttrs.put(subschemaSubentryAttr.getAttributeType(),
                         subschemaSubentryAttrs);
      }
      else
      {
        dseOperationalAttrs.put(subschemaSubentryAttr.getAttributeType(),
                                subschemaSubentryAttrs);
      }
    }
    // Add all the standard "static" attributes.
    for (Attribute a : staticDSEAttributes)
    {
@@ -741,8 +712,10 @@
    // Construct and return the entry.
    return new Entry(rootDSEDN, dseObjectClasses, dseUserAttrs,
    Entry e = new Entry(rootDSEDN, dseObjectClasses, dseUserAttrs,
                     dseOperationalAttrs);
    e.processVirtualAttributes();
    return e;
  }
opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -952,7 +952,10 @@
    // Construct and return the entry.
    return new Entry(entryDN, schemaObjectClasses, userAttrs, operationalAttrs);
    Entry e = new Entry(entryDN, schemaObjectClasses, userAttrs,
                        operationalAttrs);
    e.processVirtualAttributes();
    return e;
  }
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -256,12 +256,13 @@
    //Try to decode the entry based on the version number. On later versions,
    //a case could be written to upgrade entries if it is not the current
    //version
    Entry entry = null;
    switch(entryVersion)
    {
      case JebFormat.FORMAT_VERSION :
        try
        {
          return JebFormat.entryFromDatabase(entryBytes);
          entry = JebFormat.entryFromDatabase(entryBytes);
        }
        catch (Exception e)
        {
@@ -269,6 +270,8 @@
          String message = getMessage(msgID, id.toString());
          throw new JebException(msgID, message);
        }
        break;
      //case 0x00                     :
      //  Call upgrade method? Call 0x00 decode method?
      default   :
@@ -276,6 +279,13 @@
        String message = getMessage(msgID, id.toString(), entryVersion);
        throw new JebException(msgID, message);
    }
    if (entry != null)
    {
      entry.processVirtualAttributes();
    }
    return entry;
  }
  /**
opends/src/server/org/opends/server/backends/jeb/JebFormat.java
@@ -356,6 +356,10 @@
    {
      for (Attribute a : list)
      {
        if (a.isVirtual())
        {
          continue;
        }
        userAttrElements.add(new LDAPAttribute(a).encode());
      }
    }
@@ -367,6 +371,10 @@
    {
      for (Attribute a : list)
      {
        if (a.isVirtual())
        {
          continue;
        }
        opAttrElements.add(new LDAPAttribute(a).encode());
      }
    }
opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -84,9 +84,6 @@
       extends Backend
       implements ConfigurableComponent
{
  /**
   * The set of time units that will be used for expressing the task retention
   * time.
@@ -157,8 +154,6 @@
  {
    super();
    // Perform all initialization in initializeBackend.
  }
@@ -586,8 +581,10 @@
  public void addEntry(Entry entry, AddOperation addOperation)
         throws DirectoryException
  {
    Entry e = entry.duplicate(false);
    // Get the DN for the entry and then get its parent.
    DN entryDN = entry.getDN();
    DN entryDN = e.getDN();
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null)
@@ -603,7 +600,7 @@
    // treat the provided entry like a scheduled task.
    if (parentDN.equals(scheduledTaskParentDN))
    {
      Task task = taskScheduler.entryToScheduledTask(entry, addOperation);
      Task task = taskScheduler.entryToScheduledTask(e, addOperation);
      taskScheduler.scheduleTask(task, true);
      return;
    }
@@ -612,7 +609,7 @@
    // treat the provided entry like a recurring task.
    if (parentDN.equals(recurringTaskParentDN))
    {
      RecurringTask recurringTask = taskScheduler.entryToRecurringTask(entry);
      RecurringTask recurringTask = taskScheduler.entryToRecurringTask(e);
      taskScheduler.addRecurringTask(recurringTask, true);
      return;
    }
opends/src/server/org/opends/server/backends/task/TaskScheduler.java
@@ -1360,7 +1360,7 @@
   */
  public Entry getTaskRootEntry()
  {
    return taskRootEntry;
    return taskRootEntry.duplicate(true);
  }
@@ -1374,7 +1374,7 @@
   */
  public Entry getScheduledTaskParentEntry()
  {
    return scheduledTaskParentEntry;
    return scheduledTaskParentEntry.duplicate(true);
  }
@@ -1388,7 +1388,7 @@
   */
  public Entry getRecurringTaskParentEntry()
  {
    return recurringTaskParentEntry;
    return recurringTaskParentEntry.duplicate(true);
  }
@@ -1540,7 +1540,7 @@
        if (scheduledTaskEntryDN.equals(taskEntry.getDN()))
        {
          return taskEntry;
          return taskEntry.duplicate(true);
        }
      }
@@ -1586,7 +1586,7 @@
        try
        {
          Entry e = t.getTaskEntry();
          Entry e = t.getTaskEntry().duplicate(true);
          if (filter.matchesEntry(e))
          {
            if (! searchOperation.returnEntry(e, null))
@@ -1691,7 +1691,7 @@
        if (recurringTaskEntryDN.equals(recurringTaskEntry.getDN()))
        {
          return recurringTaskEntry;
          return recurringTaskEntry.duplicate(true);
        }
      }
@@ -1737,7 +1737,7 @@
        try
        {
          Entry e = rt.getRecurringTaskEntry();
          Entry e = rt.getRecurringTaskEntry().duplicate(true);
          if (filter.matchesEntry(e))
          {
            if (! searchOperation.returnEntry(e, null))
opends/src/server/org/opends/server/config/ConfigEntry.java
@@ -459,7 +459,7 @@
   */
  public ConfigEntry duplicate()
  {
    return new ConfigEntry(entry.duplicate(), parent);
    return new ConfigEntry(entry.duplicate(false), parent);
  }
opends/src/server/org/opends/server/core/AddOperation.java
@@ -2014,7 +2014,7 @@
            if (postReadRequest != null)
            {
              Entry addedEntry = entry.duplicate();
              Entry addedEntry = entry.duplicate(true);
              if (! postReadRequest.allowsAttribute(
                         DirectoryServer.getObjectClassAttributeType()))
opends/src/server/org/opends/server/core/DeleteOperation.java
@@ -1085,7 +1085,7 @@
          if (preReadRequest != null)
          {
            Entry entryCopy = entry.duplicate();
            Entry entryCopy = entry.duplicate(true);
            if (! preReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -296,10 +296,6 @@
  // between the mechanism name and the handler).
  private ConcurrentHashMap<String,SASLMechanismHandler> saslMechanismHandlers;
  // The set of virtual attributes defined in the server (mapped between the
  // lowercase names and the virtual attributes).
  private ConcurrentHashMap<String,VirtualAttribute> virtualAttributes;
  // The connection handler configuration manager for the Directory Server.
  private ConnectionHandlerConfigManager connectionHandlerConfigManager;
@@ -341,6 +337,9 @@
  private CopyOnWriteArrayList<SynchronizationProvider>
               synchronizationProviders;
  // The set of virtual attributes defined in the server.
  private CopyOnWriteArrayList<VirtualAttributeRule> virtualAttributes;
  // The set of backend initialization listeners registered with the Directory
  // Server.
  private CopyOnWriteArraySet<BackendInitializationListener>
@@ -511,6 +510,10 @@
  // The trust manager provider configuration manager for the Directory Server.
  private TrustManagerProviderConfigManager trustManagerProviderConfigManager;
  // The virtual attribute provider configuration manager for the Directory
  // Server.
  private VirtualAttributeConfigManager virtualAttributeConfigManager;
  // The work queue that will be used to service client requests.
  private WorkQueue workQueue;
@@ -631,7 +634,7 @@
    directoryServer.supportedControls = new TreeSet<String>();
    directoryServer.supportedFeatures = new TreeSet<String>();
    directoryServer.virtualAttributes =
         new ConcurrentHashMap<String,VirtualAttribute>();
         new CopyOnWriteArrayList<VirtualAttributeRule>();
    directoryServer.connectionHandlers =
         new CopyOnWriteArrayList<ConnectionHandler>();
    directoryServer.identityMappers =
@@ -2218,6 +2221,8 @@
    supportedControls.add(OID_MATCHED_VALUES);
    supportedControls.add(OID_LDAP_SUBENTRIES);
    supportedControls.add(OID_PASSWORD_POLICY_CONTROL);
    supportedControls.add(OID_REAL_ATTRS_ONLY);
    supportedControls.add(OID_VIRTUAL_ATTRS_ONLY);
  }
@@ -2314,7 +2319,8 @@
  private void initializeVirtualAttributes()
          throws ConfigException, InitializationException
  {
    // NYI
    virtualAttributeConfigManager = new VirtualAttributeConfigManager();
    virtualAttributeConfigManager.initializeVirtualAttributes();
  }
@@ -3926,6 +3932,113 @@
  /**
   * Retrieves the set of virtual attribute rules registered with the Directory
   * Server.
   *
   * @return  The set of virtual attribute rules registered with the Directory
   *          Server.
   */
  public static List<VirtualAttributeRule> getVirtualAttributes()
  {
    return directoryServer.virtualAttributes;
  }
  /**
   * Retrieves the set of virtual attribute rules registered with the Directory
   * Server that are applicable to the provided entry.
   *
   * @param  entry  The entry for which to retrieve the applicable virtual
   *                attribute rules.
   *
   * @return  The set of virtual attribute rules registered with the Directory
   *          Server that apply to the given entry.  It may be an empty list if
   *          there are no applicable virtual attribute rules.
   */
  public static List<VirtualAttributeRule> getVirtualAttributes(Entry entry)
  {
    LinkedList<VirtualAttributeRule> ruleList =
         new LinkedList<VirtualAttributeRule>();
    for (VirtualAttributeRule rule : directoryServer.virtualAttributes)
    {
      if (rule.appliesToEntry(entry))
      {
        ruleList.add(rule);
      }
    }
    return ruleList;
  }
  /**
   * Registers the provided virtual attribute rule with the Directory Server.
   *
   * @param  rule  The virtual attribute rule to be registered.
   */
  public static void registerVirtualAttribute(VirtualAttributeRule rule)
  {
    synchronized (directoryServer.virtualAttributes)
    {
      directoryServer.virtualAttributes.add(rule);
    }
  }
  /**
   * Deregisters the provided virtual attribute rule with the Directory Server.
   *
   * @param  rule  The virutal attribute rule to be deregistered.
   */
  public static void deregisterVirtualAttribute(VirtualAttributeRule rule)
  {
    synchronized (directoryServer.virtualAttributes)
    {
      directoryServer.virtualAttributes.remove(rule);
    }
  }
  /**
   * Replaces the specified virtual attribute rule in the set of virtual
   * attributes registered with the Directory Server.  If the old rule cannot
   * be found in the list, then the set of registered virtual attributes is not
   * updated.
   *
   * @param  oldRule  The existing rule that should be replaced with the new
   *                  rule.
   * @param  newRule  The new rule that should be used in place of the existing
   *                  rule.
   *
   * @return  {@code true} if the old rule was found and replaced with the new
   *          version, or {@code false} if it was not.
   */
  public static boolean replaceVirtualAttribute(VirtualAttributeRule oldRule,
                                                VirtualAttributeRule newRule)
  {
    synchronized (directoryServer.virtualAttributes)
    {
      int pos = directoryServer.virtualAttributes.indexOf(oldRule);
      if (pos >= 0)
      {
        directoryServer.virtualAttributes.set(pos, newRule);
        return true;
      }
      else
      {
        return false;
      }
    }
  }
  /**
   * Retrieves a reference to the JMX MBean server that is associated with the
   * Directory Server.
   *
opends/src/server/org/opends/server/core/ModifyDNOperation.java
@@ -1383,7 +1383,7 @@
        // Duplicate the entry and set its new DN.  Also, create an empty list
        // to hold the attribute-level modifications.
        newEntry = currentEntry.duplicate();
        newEntry = currentEntry.duplicate(false);
        newEntry.setDN(newDN);
        modifications = new ArrayList<Modification>();
@@ -1818,7 +1818,7 @@
          if (preReadRequest != null)
          {
            Entry entry = currentEntry.duplicate();
            Entry entry = currentEntry.duplicate(true);
            if (! preReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
@@ -1869,7 +1869,7 @@
          if (postReadRequest != null)
          {
            Entry entry = newEntry.duplicate();
            Entry entry = newEntry.duplicate(true);
            if (! postReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -1189,7 +1189,7 @@
        // Create a duplicate of the entry and apply the changes to it.
        modifiedEntry = currentEntry.duplicate();
        modifiedEntry = currentEntry.duplicate(false);
        if (! noOp)
        {
@@ -2605,7 +2605,7 @@
          if (preReadRequest != null)
          {
            Entry entry = currentEntry.duplicate();
            Entry entry = currentEntry.duplicate(true);
            if (! preReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
@@ -2656,7 +2656,7 @@
          if (postReadRequest != null)
          {
            Entry entry = modifiedEntry.duplicate();
            Entry entry = modifiedEntry.duplicate(true);
            if (! postReadRequest.allowsAttribute(
                       DirectoryServer.getObjectClassAttributeType()))
opends/src/server/org/opends/server/core/SearchOperation.java
@@ -102,9 +102,6 @@
                  PostOperationSearchOperation, PostResponseSearchOperation,
                  SearchEntrySearchOperation, SearchReferenceSearchOperation
{
  // Indicates whether a search result done response has been sent to the
  // client.
  private AtomicBoolean responseSent;
@@ -116,12 +113,18 @@
  // entries.
  private boolean includeUsableControl;
  // Indicates whether to only real attributes should be returned.
  private boolean realAttributesOnly;
  // Indicates whether LDAP subentries should be returned.
  private boolean returnLDAPSubentries;
  // Indicates whether to include attribute types only or both types and values.
  private boolean typesOnly;
  // Indicates whether to only virtual attributes should be returned.
  private boolean virtualAttributesOnly;
  // The raw, unprocessed base DN as included in the request from the client.
  private ByteString rawBaseDN;
@@ -274,6 +277,8 @@
    persistentSearch       = null;
    returnLDAPSubentries   = false;
    matchedValuesControl   = null;
    realAttributesOnly     = false;
    virtualAttributesOnly  = false;
  }
@@ -865,7 +870,8 @@
    Entry entryToReturn;
    if ((attributes == null) || attributes.isEmpty())
    {
      entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly);
      entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly,
                                                                  true);
    }
    else
    {
@@ -1028,6 +1034,16 @@
    }
    if (realAttributesOnly)
    {
      entryToReturn.stripVirtualAttributes();
    }
    else if (virtualAttributesOnly)
    {
      entryToReturn.stripRealAttributes();
    }
    // If there is a matched values control, then further pare down the entry
    // based on the filters that it contains.
    if ((matchedValuesControl != null) && (! typesOnly))
@@ -1877,6 +1893,14 @@
          {
            includeUsableControl = true;
          }
          else if (oid.equals(OID_REAL_ATTRS_ONLY))
          {
            realAttributesOnly = true;
          }
          else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
          {
            virtualAttributesOnly = true;
          }
          // NYI -- Add support for additional controls.
          else if (c.isCritical())
opends/src/server/org/opends/server/core/VirtualAttributeConfigManager.java
New file
@@ -0,0 +1,578 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.VirtualAttributeRule;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.ConfigMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines a utility that will be used to manage the set of
 * virtual attribute providers defined in the Directory Server.  It will
 * initialize the providers when the server starts, and then will manage any
 * additions, removals, or modifications to any virtual attribute providers
 * while the server is running.
 */
public class VirtualAttributeConfigManager
       implements ConfigurationChangeListener<VirtualAttributeCfg>,
                  ConfigurationAddListener<VirtualAttributeCfg>,
                  ConfigurationDeleteListener<VirtualAttributeCfg>
{
  // A mapping between the DNs of the config entries and the associated
  // virtual attribute rules.
  private ConcurrentHashMap<DN,VirtualAttributeRule> rules;
  /**
   * Creates a new instance of this virtual attribute config manager.
   */
  public VirtualAttributeConfigManager()
  {
    rules = new ConcurrentHashMap<DN,VirtualAttributeRule>();
  }
  /**
   * Initializes all virtual attribute providers currently defined in the
   * Directory Server configuration.  This should only be called at Directory
   * Server startup.
   *
   * @throws  ConfigException  If a configuration problem causes the virtual
   *                           attribute provider initialization process to
   *                           fail.
   *
   * @throws  InitializationException  If a problem occurs while initializing
   *                                   the virtual attribute providers that is
   *                                   not related to the server configuration.
   */
  public void initializeVirtualAttributes()
         throws ConfigException, InitializationException
  {
    // Get the root configuration object.
    ServerManagementContext managementContext =
         ServerManagementContext.getInstance();
    RootCfg rootConfiguration =
         managementContext.getRootConfiguration();
    // Register as an add and delete listener with the root configuration so we
    // can be notified if any virtual attribute provider entries are added or
    // removed.
    rootConfiguration.addVirtualAttributeAddListener(this);
    rootConfiguration.addVirtualAttributeDeleteListener(this);
    //Initialize the existing virtual attribute providers.
    for (String providerName : rootConfiguration.listVirtualAttributes())
    {
      VirtualAttributeCfg cfg =
           rootConfiguration.getVirtualAttribute(providerName);
      cfg.addChangeListener(this);
      if (cfg.isEnabled())
      {
        String className = cfg.getProviderClass();
        try
        {
          VirtualAttributeProvider<? extends VirtualAttributeCfg> provider =
               loadProvider(className, cfg);
          LinkedHashSet<SearchFilter> filters =
               new LinkedHashSet<SearchFilter>();
          for (String filterString : cfg.getFilter())
          {
            try
            {
              filters.add(SearchFilter.createFilterFromString(filterString));
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, de);
              }
              int    msgID   = MSGID_CONFIG_VATTR_INVALID_SEARCH_FILTER;
              String message = getMessage(msgID, filterString,
                                          String.valueOf(cfg.dn()),
                                          de.getErrorMessage());
              throw new ConfigException(msgID, message, de);
            }
          }
          if (cfg.getAttributeType().isSingleValue())
          {
            if (provider.isMultiValued())
            {
              int    msgID   = MSGID_CONFIG_VATTR_SV_TYPE_WITH_MV_PROVIDER;
              String message = getMessage(msgID, String.valueOf(cfg.dn()),
                                          cfg.getAttributeType().getNameOrOID(),
                                          className);
              throw new ConfigException(msgID, message);
            }
            else if (cfg.getConflictBehavior() ==
                     VirtualAttributeCfgDefn.ConflictBehavior.
                          MERGE_REAL_AND_VIRTUAL)
            {
              int    msgID   = MSGID_CONFIG_VATTR_SV_TYPE_WITH_MERGE_VALUES;
              String message = getMessage(msgID, String.valueOf(cfg.dn()),
                                    cfg.getAttributeType().getNameOrOID());
              throw new ConfigException(msgID, message);
            }
          }
          VirtualAttributeRule rule =
               new VirtualAttributeRule(cfg.getAttributeType(), provider,
                                        cfg.getBaseDN(), cfg.getGroupDN(),
                                        filters, cfg.getConflictBehavior());
          rules.put(cfg.dn(), rule);
          DirectoryServer.registerVirtualAttribute(rule);
        }
        catch (InitializationException ie)
        {
          logError(ErrorLogCategory.CONFIGURATION,
                   ErrorLogSeverity.SEVERE_ERROR,
                   ie.getMessage(), ie.getMessageID());
          continue;
        }
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationAddAcceptable(
                      VirtualAttributeCfg configuration,
                      List<String> unacceptableReasons)
  {
    if (configuration.isEnabled())
    {
      // Get the name of the class and make sure we can instantiate it as a
      // virtual attribute provider.
      String className = configuration.getProviderClass();
      try
      {
        loadProvider(className, null);
      }
      catch (InitializationException ie)
      {
        unacceptableReasons.add(ie.getMessage());
        return false;
      }
    }
    // If there were any search filters provided, then make sure they are all
    // valid.
    for (String filterString : configuration.getFilter())
    {
      try
      {
        SearchFilter.createFilterFromString(filterString);
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, de);
        }
        int    msgID   = MSGID_CONFIG_VATTR_INVALID_SEARCH_FILTER;
        String message = getMessage(msgID, filterString,
                                    String.valueOf(configuration.dn()),
                                    de.getErrorMessage());
        unacceptableReasons.add(message);
        return false;
      }
    }
    // If we've gotten here, then it's fine.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(
                                 VirtualAttributeCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    configuration.addChangeListener(this);
    if (! configuration.isEnabled())
    {
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Make sure that we can parse all of the search filters.
    LinkedHashSet<SearchFilter> filters =
         new LinkedHashSet<SearchFilter>();
    for (String filterString : configuration.getFilter())
    {
      try
      {
        filters.add(SearchFilter.createFilterFromString(filterString));
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, de);
        }
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
        }
        int    msgID   = MSGID_CONFIG_VATTR_INVALID_SEARCH_FILTER;
        String message = getMessage(msgID, filterString,
                                    String.valueOf(configuration.dn()),
                                    de.getErrorMessage());
        messages.add(message);
      }
    }
    // Get the name of the class and make sure we can instantiate it as a
    // certificate mapper.
    VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null;
    if (resultCode == ResultCode.SUCCESS)
    {
      String className = configuration.getProviderClass();
      try
      {
        provider = loadProvider(className, configuration);
      }
      catch (InitializationException ie)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
        messages.add(ie.getMessage());
      }
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      VirtualAttributeRule rule =
           new VirtualAttributeRule(configuration.getAttributeType(), provider,
                                    configuration.getBaseDN(),
                                    configuration.getGroupDN(),
                                    filters,
                                    configuration.getConflictBehavior());
      rules.put(configuration.dn(), rule);
      DirectoryServer.registerVirtualAttribute(rule);
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationDeleteAcceptable(
                      VirtualAttributeCfg configuration,
                      List<String> unacceptableReasons)
  {
    // We will always allow getting rid of a virtual attribute rule.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(
                                 VirtualAttributeCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    VirtualAttributeRule rule = rules.remove(configuration.dn());
    if (rule != null)
    {
      DirectoryServer.deregisterVirtualAttribute(rule);
      rule.getProvider().finalizeVirtualAttributeProvider();
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
                      VirtualAttributeCfg configuration,
                      List<String> unacceptableReasons)
  {
    if (configuration.isEnabled())
    {
      // Get the name of the class and make sure we can instantiate it as a
      // virtual attribute provider.
      String className = configuration.getProviderClass();
      try
      {
        loadProvider(className, null);
      }
      catch (InitializationException ie)
      {
        unacceptableReasons.add(ie.getMessage());
        return false;
      }
    }
    // If there were any search filters provided, then make sure they are all
    // valid.
    for (String filterString : configuration.getFilter())
    {
      try
      {
        SearchFilter.createFilterFromString(filterString);
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, de);
        }
        int    msgID   = MSGID_CONFIG_VATTR_INVALID_SEARCH_FILTER;
        String message = getMessage(msgID, filterString,
                                    String.valueOf(configuration.dn()),
                                    de.getErrorMessage());
        unacceptableReasons.add(message);
        return false;
      }
    }
    // If we've gotten here, then it's fine.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
                                 VirtualAttributeCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // Get the existing rule if it's already enabled.
    VirtualAttributeRule existingRule = rules.get(configuration.dn());
    // If the new configuration has the rule disabled, then disable it if it
    // is enabled, or do nothing if it's already disabled.
    if (! configuration.isEnabled())
    {
      if (existingRule != null)
      {
        DirectoryServer.deregisterVirtualAttribute(existingRule);
        existingRule.getProvider().finalizeVirtualAttributeProvider();
      }
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
    }
    // Make sure that we can parse all of the search filters.
    LinkedHashSet<SearchFilter> filters =
         new LinkedHashSet<SearchFilter>();
    for (String filterString : configuration.getFilter())
    {
      try
      {
        filters.add(SearchFilter.createFilterFromString(filterString));
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, de);
        }
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = ResultCode.INVALID_ATTRIBUTE_SYNTAX;
        }
        int    msgID   = MSGID_CONFIG_VATTR_INVALID_SEARCH_FILTER;
        String message = getMessage(msgID, filterString,
                                    String.valueOf(configuration.dn()),
                                    de.getErrorMessage());
        messages.add(message);
      }
    }
    // Get the name of the class and make sure we can instantiate it as a
    // certificate mapper.
    VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null;
    if (resultCode == ResultCode.SUCCESS)
    {
      String className = configuration.getProviderClass();
      try
      {
        provider = loadProvider(className, configuration);
      }
      catch (InitializationException ie)
      {
        resultCode = DirectoryServer.getServerErrorResultCode();
        messages.add(ie.getMessage());
      }
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      VirtualAttributeRule rule =
           new VirtualAttributeRule(configuration.getAttributeType(), provider,
                                    configuration.getBaseDN(),
                                    configuration.getGroupDN(),
                                    filters,
                                    configuration.getConflictBehavior());
      rules.put(configuration.dn(), rule);
      if (existingRule == null)
      {
        DirectoryServer.registerVirtualAttribute(rule);
      }
      else
      {
        DirectoryServer.replaceVirtualAttribute(existingRule, rule);
        existingRule.getProvider().finalizeVirtualAttributeProvider();
      }
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * Loads the specified class, instantiates it as a certificate mapper, and
   * optionally initializes that instance.
   *
   * @param  className      The fully-qualified name of the certificate mapper
   *                        class to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the
   *                        certificate mapper, or {@code null} if the
   *                        certificate mapper should not be initialized.
   *
   * @return  The possibly initialized certificate mapper.
   *
   * @throws  InitializationException  If a problem occurred while attempting to
   *                                   initialize the certificate mapper.
   */
  private VirtualAttributeProvider<? extends VirtualAttributeCfg>
               loadProvider(String className, VirtualAttributeCfg configuration)
          throws InitializationException
  {
    try
    {
      VirtualAttributeCfgDefn definition =
           VirtualAttributeCfgDefn.getInstance();
      ClassPropertyDefinition propertyDefinition =
           definition.getProviderClassPropertyDefinition();
      Class<? extends VirtualAttributeProvider> providerClass =
           propertyDefinition.loadClass(className,
                                        VirtualAttributeProvider.class);
      VirtualAttributeProvider<? extends VirtualAttributeCfg> provider =
           (VirtualAttributeProvider<? extends VirtualAttributeCfg>)
           providerClass.newInstance();
      if (configuration != null)
      {
        Method method =
             provider.getClass().getMethod("initializeVirtualAttributeProvider",
                  configuration.definition().getServerConfigurationClass());
        method.invoke(provider, configuration);
      }
      return provider;
    }
    catch (Exception e)
    {
      int msgID = MSGID_CONFIG_VATTR_INITIALIZATION_FAILED;
      String message = getMessage(msgID, className,
                                  String.valueOf(configuration.dn()),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
  }
}
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -983,7 +983,7 @@
      return null;
    }
    return configEntry.getEntry();
    return configEntry.getEntry().duplicate(true);
  }
@@ -1027,6 +1027,8 @@
  public void addEntry(Entry entry, AddOperation addOperation)
         throws DirectoryException
  {
    Entry e = entry.duplicate(false);
    // If there is an add operation, then make sure that the associated user has
    // both the CONFIG_READ and CONFIG_WRITE privileges.
    if (addOperation != null)
@@ -1051,7 +1053,7 @@
    {
      // Make sure that the target DN does not already exist.  If it does, then
      // fail.
      DN entryDN = entry.getDN();
      DN entryDN = e.getDN();
      if (configEntries.containsKey(entryDN))
      {
        int    msgID   = MSGID_CONFIG_FILE_ADD_ALREADY_EXISTS;
@@ -1099,7 +1101,7 @@
      // Encapsulate the provided entry in a config entry.
      ConfigEntry newEntry = new ConfigEntry(entry, parentEntry);
      ConfigEntry newEntry = new ConfigEntry(e, parentEntry);
      // See if the parent entry has any add listeners.  If so, then iterate
@@ -1327,6 +1329,8 @@
  public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
         throws DirectoryException
  {
    Entry e = entry.duplicate(false);
    // If there is a modify operation, then make sure that the associated user
    // has both the CONFIG_READ and CONFIG_WRITE privileges.  Also, if the
    // operation targets the set of root privileges then make sure the user has
@@ -1373,7 +1377,7 @@
    try
    {
      // Get the DN of the target entry for future reference.
      DN entryDN = entry.getDN();
      DN entryDN = e.getDN();
      // Get the target entry.  If it does not exist, then fail.
@@ -1405,7 +1409,7 @@
      // Create a new config entry to use for the validation testing.
      ConfigEntry newEntry = new ConfigEntry(entry, currentEntry.getParent());
      ConfigEntry newEntry = new ConfigEntry(e, currentEntry.getParent());
      // See if there are any config change listeners registered for this entry.
@@ -1465,7 +1469,7 @@
      // We'll just overwrite the core entry in the current config entry so that
      // we keep all the registered listeners, references to the parent and
      // children, and other metadata.
      currentEntry.setEntry(entry);
      currentEntry.setEntry(e);
      writeUpdatedConfig();
@@ -1606,7 +1610,7 @@
      case BASE_OBJECT:
        // We are only interested in the base entry itself.  See if it matches
        // and if so then return the entry.
        Entry e = baseEntry.getEntry();
        Entry e = baseEntry.getEntry().duplicate(true);
        if (filter.matchesEntry(e))
        {
          searchOperation.returnEntry(e, null);
@@ -1619,7 +1623,7 @@
        // Iterate through them and return the ones that match the filter.
        for (ConfigEntry child : baseEntry.getChildren().values())
        {
          e = child.getEntry();
          e = child.getEntry().duplicate(true);
          if (filter.matchesEntry(e))
          {
            if (! searchOperation.returnEntry(e, null))
@@ -1680,7 +1684,7 @@
                                SearchOperation searchOperation)
          throws DirectoryException
  {
    Entry e = baseEntry.getEntry();
    Entry e = baseEntry.getEntry().duplicate(true);
    if (filter.matchesEntry(e))
    {
      if (! searchOperation.returnEntry(e, null))
opends/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java
New file
@@ -0,0 +1,399 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringFactory;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.VirtualAttributeRule;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class implements a virtual attribute provider that is meant to serve the
 * entryDN operational attribute as described in draft-zeilenga-ldap-entrydn.
 */
public class EntryDNVirtualAttributeProvider
       extends VirtualAttributeProvider<VirtualAttributeCfg>
{
  /**
   * Creates a new instance of this entryDN virtual attribute provider.
   */
  public EntryDNVirtualAttributeProvider()
  {
    super();
    // All initialization should be performed in the
    // initializeVirtualAttributeProvider method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeVirtualAttributeProvider(
                            VirtualAttributeCfg configuration)
         throws ConfigException, InitializationException
  {
    // No initialization is required.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMultiValued()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashSet<AttributeValue> getValues(Entry entry,
                                                 VirtualAttributeRule rule)
  {
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    String normDNString = entry.getDN().toNormalizedString();
    values.add(new AttributeValue(ByteStringFactory.create(normDNString),
                                  ByteStringFactory.create(normDNString)));
    return values;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
  {
    // This virtual attribute provider will always generate a value.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule,
                          AttributeValue value)
  {
    try
    {
      String normalizedDN    = entry.getDN().toNormalizedString();
      String normalizedValue = value.getNormalizedStringValue();
      return normalizedDN.equals(normalizedValue);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasAnyValue(Entry entry, VirtualAttributeRule rule,
                             Collection<AttributeValue> values)
  {
    String ndnString = entry.getDN().toNormalizedString();
    AttributeValue v = new AttributeValue(ByteStringFactory.create(ndnString),
                                          ByteStringFactory.create(ndnString));
    return values.contains(v);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult matchesSubstring(Entry entry,
                                          VirtualAttributeRule rule,
                                          ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    // DNs cannot be used in substring matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult greaterThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult lessThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult approximatelyEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in approximate matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}.  This virtual attribute will support search operations only
   * if one of the following is true about the search filter:
   * <UL>
   *   <LI>It is an equality filter targeting the associated attribute
   *       type.</LI>
   *   <LI>It is an AND filter in which at least one of the components is an
   *       equality filter targeting the associated attribute type.</LI>
   *   <LI>It is an OR filter in which all of the components are equality
   *       filters targeting the associated attribute type.</LI>
   * </UL>
   */
  @Override()
  public boolean isSearchable(VirtualAttributeRule rule,
                              SearchOperation searchOperation)
  {
    return isSearchable(rule.getAttributeType(), searchOperation.getFilter(),
                        0);
  }
  /**
   * Indicates whether the provided search filter is one that may be used with
   * this virtual attribute provider, optionally operating in a recursive manner
   * to make the determination.
   *
   * @param  attributeType  The attribute type used to hold the entryDN value.
   * @param  searchFilter   The search filter for which to make the
   *                        determination.
   * @param  depth          The current recursion depth for this processing.
   *
   * @return  {@code true} if the provided filter may be used with this virtual
   *          attribute provider, or {@code false} if not.
   */
  private boolean isSearchable(AttributeType attributeType, SearchFilter filter,
                               int depth)
  {
    switch (filter.getFilterType())
    {
      case AND:
        if (depth >= MAX_NESTED_FILTER_DEPTH)
        {
          return false;
        }
        for (SearchFilter f : filter.getFilterComponents())
        {
          if (isSearchable(attributeType, f, depth+1))
          {
            return true;
          }
        }
        return false;
      case OR:
        if (depth >= MAX_NESTED_FILTER_DEPTH)
        {
          return false;
        }
        for (SearchFilter f : filter.getFilterComponents())
        {
          if (! isSearchable(attributeType, f, depth+1))
          {
            return false;
          }
        }
        return true;
      case EQUALITY:
        return filter.getAttributeType().equals(attributeType);
      default:
        return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSearch(VirtualAttributeRule rule,
                            SearchOperation searchOperation)
  {
    SearchFilter      filter = searchOperation.getFilter();
    LinkedHashSet<DN> dnSet  = new LinkedHashSet<DN>();
    extractDNs(rule.getAttributeType(), filter, dnSet);
    if (dnSet.isEmpty())
    {
      return;
    }
    DN          baseDN = searchOperation.getBaseDN();
    SearchScope scope  = searchOperation.getScope();
    for (DN dn : dnSet)
    {
      if (! dn.matchesBaseAndScope(baseDN, scope))
      {
        continue;
      }
      try
      {
        Entry entry = DirectoryServer.getEntry(dn);
        if ((entry != null) && filter.matchesEntry(entry))
        {
          searchOperation.returnEntry(entry, null);
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
  }
  /**
   * Extracts the user DNs from the provided filter, operating recursively as
   * necessary, and adds them to the provided set.
   *
   * @param  attributeType  The attribute type holding the entryDN value.
   * @param  filter         The search filter to be processed.
   * @param  dnSet          The set into which the identified DNs should be
   *                        placed.
   */
  private void extractDNs(AttributeType attributeType, SearchFilter filter,
                          LinkedHashSet<DN> dnSet)
  {
    switch (filter.getFilterType())
    {
      case AND:
      case OR:
        for (SearchFilter f : filter.getFilterComponents())
        {
          extractDNs(attributeType, f, dnSet);
        }
        break;
      case EQUALITY:
        if (filter.getAttributeType().equals(attributeType))
        {
          try
          {
            dnSet.add(DN.decode(filter.getAssertionValue().getValue()));
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
        break;
    }
  }
}
opends/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java
New file
@@ -0,0 +1,437 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.Group;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.MemberList;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.VirtualAttributeRule;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class implements a virtual attribute provider that is meant to serve the
 * isMemberOf operational attribute.  This attribute will be used to provide a
 * list of all groups in which the specified user is a member.
 */
public class IsMemberOfVirtualAttributeProvider
       extends VirtualAttributeProvider<VirtualAttributeCfg>
{
  /**
   * Creates a new instance of this entryDN virtual attribute provider.
   */
  public IsMemberOfVirtualAttributeProvider()
  {
    super();
    // All initialization should be performed in the
    // initializeVirtualAttributeProvider method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeVirtualAttributeProvider(
                            VirtualAttributeCfg configuration)
         throws ConfigException, InitializationException
  {
    // No initialization is required.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMultiValued()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashSet<AttributeValue> getValues(Entry entry,
                                                 VirtualAttributeRule rule)
  {
    // FIXME -- This probably isn't the most efficient implementation.
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    for (Group g : DirectoryServer.getGroupManager().getGroupInstances())
    {
      try
      {
        if (g.isMember(entry))
        {
          values.add(new AttributeValue(rule.getAttributeType(),
                                        g.getGroupDN().toString()));
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    return values;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
  {
    // FIXME -- This probably isn't the most efficient implementation.
    for (Group g : DirectoryServer.getGroupManager().getGroupInstances())
    {
      try
      {
        if (g.isMember(entry))
        {
          return true;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(Entry entry, VirtualAttributeRule rule,
                          AttributeValue value)
  {
    try
    {
      DN groupDN = DN.decode(value.getValue());
      Group g = DirectoryServer.getGroupManager().getGroupInstance(groupDN);
      if (g == null)
      {
        return false;
      }
      else
      {
        return g.isMember(entry);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasAnyValue(Entry entry, VirtualAttributeRule rule,
                             Collection<AttributeValue> values)
  {
    for (AttributeValue value : values)
    {
      if (hasValue(entry, rule, value))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult matchesSubstring(Entry entry,
                                          VirtualAttributeRule rule,
                                          ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    // DNs cannot be used in substring matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult greaterThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult lessThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult approximatelyEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in approximate matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}.  This virtual attribute will support search operations only
   * if one of the following is true about the search filter:
   * <UL>
   *   <LI>It is an equality filter targeting the associated attribute
   *       type.</LI>
   *   <LI>It is an AND filter in which at least one of the components is an
   *       equality filter targeting the associated attribute type.</LI>
   * </UL>
   */
  @Override()
  public boolean isSearchable(VirtualAttributeRule rule,
                              SearchOperation searchOperation)
  {
    return isSearchable(rule.getAttributeType(), searchOperation.getFilter(),
                        0);
  }
  /**
   * Indicates whether the provided search filter is one that may be used with
   * this virtual attribute provider, optionally operating in a recursive manner
   * to make the determination.
   *
   * @param  attributeType  The attribute type used to hold the entryDN value.
   * @param  searchFilter   The search filter for which to make the
   *                        determination.
   * @param  depth          The current recursion depth for this processing.
   *
   * @return  {@code true} if the provided filter may be used with this virtual
   *          attribute provider, or {@code false} if not.
   */
  private boolean isSearchable(AttributeType attributeType, SearchFilter filter,
                               int depth)
  {
    switch (filter.getFilterType())
    {
      case AND:
        if (depth >= MAX_NESTED_FILTER_DEPTH)
        {
          return false;
        }
        for (SearchFilter f : filter.getFilterComponents())
        {
          if (isSearchable(attributeType, f, depth+1))
          {
            return true;
          }
        }
        return false;
      case EQUALITY:
        return filter.getAttributeType().equals(attributeType);
      default:
        return false;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSearch(VirtualAttributeRule rule,
                            SearchOperation searchOperation)
  {
    SearchFilter filter = searchOperation.getFilter();
    Group group = extractGroup(rule.getAttributeType(), filter);
    if (group == null)
    {
      return;
    }
    DN          baseDN = searchOperation.getBaseDN();
    SearchScope scope  = searchOperation.getScope();
    try
    {
      MemberList  memberList = group.getMembers();
      while (memberList.hasMoreMembers())
      {
        try
        {
          Entry e = memberList.nextMemberEntry();
          if (e.matchesBaseAndScope(baseDN, scope) &&
              filter.matchesEntry(e))
          {
            searchOperation.returnEntry(e, null);
          }
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
    }
    catch (DirectoryException de)
    {
      searchOperation.setResponseData(de);
    }
  }
  /**
   * Extracts the first group DN encountered in the provided filter, operating
   * recursively as necessary.
   *
   * @param  attributeType  The attribute type holding the entryDN value.
   * @param  filter         The search filter to be processed.
   *
   * @return  The first group encountered in the provided filter, or
   *          {@code null} if there is no match.
   */
  private Group extractGroup(AttributeType attributeType, SearchFilter filter)
  {
    switch (filter.getFilterType())
    {
      case AND:
        for (SearchFilter f : filter.getFilterComponents())
        {
          Group g = extractGroup(attributeType, f);
          if (g != null)
          {
            return g;
          }
        }
        break;
      case EQUALITY:
        if (filter.getAttributeType().equals(attributeType))
        {
          try
          {
            DN dn = DN.decode(filter.getAssertionValue().getValue());
            return DirectoryServer.getGroupManager().getGroupInstance(dn);
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              debugCaught(DebugLogLevel.ERROR, e);
            }
          }
        }
        break;
    }
    return null;
  }
}
opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java
New file
@@ -0,0 +1,210 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.LinkedHashSet;
import java.util.List;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.VirtualAttributeRule;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * This class implements a virtual attribute provider that is meant to serve the
 * subschemaSubentry operational attribute as described in RFC 4512.
 */
public class SubschemaSubentryVirtualAttributeProvider
       extends VirtualAttributeProvider<VirtualAttributeCfg>
{
  /**
   * Creates a new instance of this subschemaSubentry virtual attribute
   * provider.
   */
  public SubschemaSubentryVirtualAttributeProvider()
  {
    super();
    // All initialization should be performed in the
    // initializeVirtualAttributeProvider method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeVirtualAttributeProvider(
                            VirtualAttributeCfg configuration)
         throws ConfigException, InitializationException
  {
    // No initialization is required.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isMultiValued()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashSet<AttributeValue> getValues(Entry entry,
                                                 VirtualAttributeRule rule)
  {
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    values.add(new AttributeValue(rule.getAttributeType(),
                                  DirectoryServer.getSchemaDN().toString()));
    return values;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult matchesSubstring(Entry entry,
                                          VirtualAttributeRule rule,
                                          ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    // DNs cannot be used in substring matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult greaterThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult lessThanOrEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in ordering matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult approximatelyEqualTo(Entry entry,
                              VirtualAttributeRule rule,
                              AttributeValue value)
  {
    // DNs cannot be used in approximate matching.
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}.  This virtual attribute will support search operations only
   * if one of the following is true about the search filter:
   * <UL>
   *   <LI>It is an equality filter targeting the associated attribute
   *       type.</LI>
   *   <LI>It is an AND filter in which at least one of the components is an
   *       equality filter targeting the associated attribute type.</LI>
   *   <LI>It is an OR filter in which all of the components are equality
   *       filters targeting the associated attribute type.</LI>
   * </UL>
   */
  @Override()
  public boolean isSearchable(VirtualAttributeRule rule,
                              SearchOperation searchOperation)
  {
    // This attribute is not searchable, since it will have the same value in
    // tons of entries.
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSearch(VirtualAttributeRule rule,
                            SearchOperation searchOperation)
  {
    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
    int    msgID   = MSGID_SUBSCHEMASUBENTRY_VATTR_NOT_SEARCHABLE;
    String message = getMessage(msgID, rule.getAttributeType().getNameOrOID());
  }
}
opends/src/server/org/opends/server/interop/LazyDN.java
@@ -29,11 +29,11 @@
import org.opends.server.types.DN;
import org.opends.server.types.RDN;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.RDN;
import org.opends.server.types.SearchScope;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
@@ -57,9 +57,6 @@
public class LazyDN
       extends DN
{
  /**
   * The serial version identifier required to satisfy the compiler because this
   * class implements the {@code java.io.Serializable} interface.  This value
@@ -229,6 +226,18 @@
   * {@inheritDoc}
   */
  @Override()
  public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
         throws RuntimeException
  {
    return getDecodedDN().matchesBaseAndScope(baseDN, scope);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean equals(Object o)
         throws RuntimeException
  {
opends/src/server/org/opends/server/messages/ConfigMessages.java
@@ -6528,6 +6528,51 @@
  /**
   * The message ID for the message that will be used if a virtual attribute
   * definition has an invalid search filter.  This takes three arguments, which
   * are the filter string, the configuration entry DN, and a message explaining
   * the problem that occurred.
   */
  public static final int MSGID_CONFIG_VATTR_INVALID_SEARCH_FILTER =
       CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_ERROR | 649;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to load and/or initialize a class as a virtual attribute provider.
   * This takes three arguments, which are the class name, the configuration
   * entry DN, and string representation of the exception that was caught.
   */
  public static final int MSGID_CONFIG_VATTR_INITIALIZATION_FAILED =
       CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_ERROR | 650;
  /**
   * The message ID for the message that will be used if the configured
   * attribute type is single-valued, but the virtual attribute provider may
   * generate multiple values.  This takes three arguments, which are the DN of
   * the configuration entry, the name or OID of the attribute type, and the
   * name of the virtual attribute provider class.
   */
  public static final int MSGID_CONFIG_VATTR_SV_TYPE_WITH_MV_PROVIDER =
       CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_ERROR | 651;
  /**
   * The message ID for the message that will be used if the configured
   * attribute type is single-valued, but the conflict behavior is to merge the
   * real and virtual values.  This takes two arguments, which are the DN of
   * the configuration entry and the name or OID of the attribute type.
   */
  public static final int MSGID_CONFIG_VATTR_SV_TYPE_WITH_MERGE_VALUES =
       CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_ERROR | 652;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -9383,6 +9428,24 @@
    registerMessage(MSGID_CONFIG_CHANGE_RESULT_MESSAGES,
                    "%s.%s succeeded but generated the following messages " +
                    "for entry %s:  %s.");
    registerMessage(MSGID_CONFIG_VATTR_INVALID_SEARCH_FILTER,
                    "Unable to parse value \"%s\" from config entry \"%s\" " +
                    "as a valid search filter:  %s.");
    registerMessage(MSGID_CONFIG_VATTR_SV_TYPE_WITH_MV_PROVIDER,
                    "The virtual attribute configuration in entry \"%s\" is " +
                    "not valid because attribute type %s is single-valued " +
                    "but provider %s may generate multiple values.");
    registerMessage(MSGID_CONFIG_VATTR_SV_TYPE_WITH_MERGE_VALUES,
                    "The virtual attribute configuration in entry \"%s\" is " +
                    "not valid because attribute type %s is single-valued " +
                    "but the conflict behavior is configured to merge real " +
                    "and virtual values.");
    registerMessage(MSGID_CONFIG_VATTR_INITIALIZATION_FAILED,
                    "An error occurred while trying to load an instance " +
                    "of class %s referenced in configuration entry %s as a " +
                    "virtual attribute provider:  %s.");
  }
}
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -4826,6 +4826,16 @@
  /**
   * The message ID for the message that will be used if a search operation has
   * a filter targeting the subschemaSubentry virtual attribute, which is not
   * searchable.  This takes a single argument, which is the name of the
   * subschemaSubentry attribute type.
   */
  public static final int MSGID_SUBSCHEMASUBENTRY_VATTR_NOT_SEARCHABLE =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 459;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -6962,6 +6972,11 @@
                    "The provided password does not contain enough unique " +
                    "characters.  The minimum number of unique characters " +
                    "that may appear in a user password is %d.");
    registerMessage(MSGID_SUBSCHEMASUBENTRY_VATTR_NOT_SEARCHABLE,
                    "The %s attribute is not searchable and should not be " +
                    "included in otherwise unindexed search filters.");
  }
}
opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
@@ -102,8 +102,6 @@
public class InternalClientConnection
       extends ClientConnection
{
  // The message ID counter to use for internal connections.
  private static AtomicInteger nextMessageID;
@@ -207,9 +205,9 @@
      this.authenticationInfo =
           new AuthenticationInfo(internalUserEntry, true);
      super.setAuthenticationInfo(authenticationInfo);
      setSizeLimit(0);
      setTimeLimit(0);
      setLookthroughLimit(0);
      super.setSizeLimit(0);
      super.setTimeLimit(0);
      super.setLookthroughLimit(0);
    }
    catch (DirectoryException de)
    {
@@ -257,9 +255,9 @@
    this.authenticationInfo = authInfo;
    super.setAuthenticationInfo(authInfo);
    setSizeLimit(0);
    setTimeLimit(0);
    setLookthroughLimit(0);
    super.setSizeLimit(0);
    super.setTimeLimit(0);
    super.setLookthroughLimit(0);
    connectionID  = nextConnectionID.getAndDecrement();
    operationList = new LinkedList<Operation>();
@@ -457,6 +455,42 @@
  /**
   * {@inheritDoc}
   */
  @Override()
  public void setSizeLimit(int sizeLimit)
  {
    // No implementation required.  We never want to set a nonzero
    // size limit for internal client connections.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void setLookthroughLimit(int lookthroughLimit)
  {
    // No implementation required.  We never want to set a nonzero
    // lookthrough limit for internal client connections.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void setTimeLimit(int timeLimit)
  {
    // No implementation required.  We never want to set a nonzero
    // time limit for internal client connections.
  }
  /**
   * Indicates whether this client connection is currently using a
   * secure mechanism to communicate with the server.  Note that this
   * may change over time based on operations performed by the client
opends/src/server/org/opends/server/tools/LDAPToolUtils.java
@@ -111,6 +111,16 @@
    {
      controlOID = OID_SUBTREE_DELETE_CONTROL;
    }
    else if (lowerOID.equals("realattrsonly") ||
             lowerOID.equals("realattributesonly"))
    {
      controlOID = OID_REAL_ATTRS_ONLY;
    }
    else if (lowerOID.equals("virtualattrsonly") ||
             lowerOID.equals("virtualattributesonly"))
    {
      controlOID = OID_VIRTUAL_ATTRS_ONLY;
    }
    if (idx < 0)
    {
opends/src/server/org/opends/server/types/Attribute.java
@@ -40,10 +40,7 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.util.Base64;
import static
    org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static
    org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -55,9 +52,6 @@
 */
public class Attribute
{
  // The attribute type for this attribute.
  private final AttributeType attributeType;
@@ -385,7 +379,7 @@
   */
  public boolean hasValue()
  {
    return (! values.isEmpty());
    return (! getValues().isEmpty());
  }
@@ -400,7 +394,7 @@
   */
  public boolean hasValue(AttributeValue value)
  {
    return values.contains(value);
    return getValues().contains(value);
  }
@@ -420,7 +414,7 @@
  {
    for (AttributeValue value : values)
    {
      if (! this.values.contains(value))
      if (! getValues().contains(value))
      {
        return false;
      }
@@ -447,7 +441,7 @@
  {
    for (AttributeValue value : values)
    {
      if (this.values.contains(value))
      if (getValues().contains(value))
      {
        return true;
      }
@@ -570,7 +564,7 @@
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue value : values)
    for (AttributeValue value : getValues())
    {
      try
      {
@@ -639,7 +633,7 @@
    }
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue v : values)
    for (AttributeValue v : getValues())
    {
      try
      {
@@ -708,7 +702,7 @@
    }
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue v : values)
    for (AttributeValue v : getValues())
    {
      try
      {
@@ -777,7 +771,7 @@
    }
    ConditionResult result = ConditionResult.FALSE;
    for (AttributeValue v : values)
    for (AttributeValue v : getValues())
    {
      try
      {
@@ -807,6 +801,20 @@
  /**
   * Indicates whether this is a virtual attribute rather than a real
   * attribute.
   *
   * @return  {@code true} if this is a virtual attribute, or
   *          {@code false} if it is a real attribute.
   */
  public boolean isVirtual()
  {
    return false;
  }
  /**
   * Creates a duplicate of this attribute that can be modified
   * without impacting this attribute.
   *
@@ -845,11 +853,7 @@
    else
    {
      LinkedHashSet<AttributeValue> valuesCopy =
           new LinkedHashSet<AttributeValue>(values.size());
      for (AttributeValue v : values)
      {
        valuesCopy.add(v);
      }
           new LinkedHashSet<AttributeValue>(getValues());
      return new Attribute(attributeType, name, optionsCopy,
                           valuesCopy);
@@ -886,12 +890,12 @@
      return false;
    }
    if (values.size() != a.values.size())
    if (getValues().size() != a.getValues().size())
    {
      return false;
    }
    if (! hasAllValues(a.values))
    if (! hasAllValues(a.getValues()))
    {
      return false;
    }
@@ -911,7 +915,7 @@
  public int hashCode()
  {
    int hashCode = attributeType.hashCode();
    for (AttributeValue value : values)
    for (AttributeValue value : getValues())
    {
      hashCode += value.hashCode();
    }
@@ -949,7 +953,7 @@
    buffer.append(", {");
    boolean firstValue = true;
    for (AttributeValue value : values)
    for (AttributeValue value : getValues())
    {
      if (! firstValue)
      {
@@ -988,7 +992,7 @@
   */
  public void toLDIF(StringBuilder buffer)
  {
    for (AttributeValue value : values)
    for (AttributeValue value : getValues())
    {
      buffer.append(name);
opends/src/server/org/opends/server/types/DN.java
@@ -29,11 +29,11 @@
/********************
 * NOTE:  Any changes to the set of public methods defined in this
 *        class or the arguments that they contain must also be made
 *        in the org.opends.server.interop.LazyDN package to ensure
 *        continued interoperability with third-party applications
 *        that rely on that functionality.
 * NOTE:  Any changes to the set of non-static public methods defined
 *        in this class or the arguments that they contain must also
 *        be made in the org.opends.server.interop.LazyDN package to
 *        ensure continued interoperability with third-party
 *        applications that rely on that functionality.
 ********************/
@@ -45,10 +45,7 @@
import org.opends.server.protocols.asn1.ASN1OctetString;
import static org.opends.server.config.ConfigConstants.*;
import static
    org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static
    org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.SchemaMessages.*;
import static org.opends.server.util.StaticUtils.*;
@@ -63,9 +60,6 @@
public class DN
       implements Comparable<DN>, Serializable
{
  /**
   * A singleton instance of the null DN (a DN with no components).
   */
@@ -429,6 +423,46 @@
  /**
   * Indicates whether this entry falls within the range of the
   * provided search base DN and scope.
   *
   * @param  baseDN  The base DN for which to make the determination.
   * @param  scope   The search scope for which to make the
   *                 determination.
   *
   * @return  <CODE>true</CODE> if this entry is within the given
   *          base and scope, or <CODE>false</CODE> if it is not.
   */
  public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
  {
    switch (scope)
    {
      case BASE_OBJECT:
        // The base DN must equal this DN.
        return equals(baseDN);
      case SINGLE_LEVEL:
        // The parent DN must equal the base DN.
        return baseDN.equals(getParent());
      case WHOLE_SUBTREE:
        // This DN must be a descendant of the provided base DN.
        return isDescendantOf(baseDN);
      case SUBORDINATE_SUBTREE:
        // This DN must be a descendant of the provided base DN, but
        // not equal to it.
        return ((! equals(baseDN)) && isDescendantOf(baseDN));
      default:
        // This is a scope that we don't recognize.
        return false;
    }
  }
  /**
   * Decodes the provided ASN.1 octet string as a DN.
   *
   * @param  dnString  The ASN.1 octet string to decode as a DN.
opends/src/server/org/opends/server/types/Entry.java
@@ -51,14 +51,7 @@
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.util.LDIFException;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static
    org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static
    org.opends.server.loggers.debug.DebugLogger.debugVerbose;
import static org.opends.server.loggers.debug.DebugLogger.debugInfo;
import static
    org.opends.server.loggers.debug.DebugLogger.debugWarning;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
@@ -90,8 +83,9 @@
public class Entry
       implements ProtocolElement
{
  // Indicates whether virtual attribute processing has been performed
  // for this entry.
  private boolean virtualAttributeProcessingPerformed;
  // The set of operational attributes for this entry.
  private Map<AttributeType,List<Attribute>> operationalAttributes;
@@ -99,6 +93,9 @@
  // The set of user attributes for this entry.
  private Map<AttributeType,List<Attribute>> userAttributes;
  // The set of suppressed real attributes for this entry.
  private Map<AttributeType,List<Attribute>> suppressedAttributes;
  // The set of objectclasses for this entry.
  private Map<ObjectClass,String> objectClasses;
@@ -139,6 +136,11 @@
  {
    attachment = null;
    schema     = DirectoryServer.getSchema();
    virtualAttributeProcessingPerformed = false;
    suppressedAttributes =
         new LinkedHashMap<AttributeType,List<Attribute>>();
    if (dn == null)
@@ -3094,10 +3096,14 @@
   * Creates a duplicate of this entry that may be altered without
   * impacting the information in this entry.
   *
   * @param  processVirtual  Indicates whether virtual attribute
   *                         processing should be performed for the
   *                         entry.
   *
   * @return  A duplicate of this entry that may be altered without
   *          impacting the information in this entry.
   */
  public Entry duplicate()
  public Entry duplicate(boolean processVirtual)
  {
    HashMap<ObjectClass,String> objectClassesCopy =
         new HashMap<ObjectClass,String>(objectClasses);
@@ -3112,8 +3118,26 @@
                  operationalAttributes.size());
    deepCopy(operationalAttributes, operationalAttrsCopy, false);
    return new Entry(dn, objectClassesCopy, userAttrsCopy,
    for (AttributeType t : suppressedAttributes.keySet())
    {
      List<Attribute> attrList = suppressedAttributes.get(t);
      if (t.isOperational())
      {
        operationalAttributes.put(t, attrList);
      }
      else
      {
        userAttributes.put(t, attrList);
      }
    }
    Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
                     operationalAttrsCopy);
    if (processVirtual)
    {
      e.processVirtualAttributes();
    }
    return e;
  }
@@ -3123,15 +3147,18 @@
   * attributes that may be altered without impacting the information
   * in this entry.
   *
   * @param  typesOnly  Indicates whether to include attribute types
   *                    only without values.
   * @param  typesOnly       Indicates whether to include attribute
   *                         types only without values.
   * @param  processVirtual  Indicates whether virtual attribute
   *                         processing should be performed for the
   *                         entry.
   *
   * @return  A duplicate of this entry that may be altered without
   *          impacting the information in this entry and that does
   *          not contain any operational attributes.
   */
  public Entry duplicateWithoutOperationalAttributes(
                    boolean typesOnly)
                    boolean typesOnly, boolean processVirtual)
  {
    HashMap<ObjectClass,String> objectClassesCopy;
    if (typesOnly)
@@ -3163,8 +3190,24 @@
    HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
         new HashMap<AttributeType,List<Attribute>>(0);
    return new Entry(dn, objectClassesCopy, userAttrsCopy,
    for (AttributeType t : suppressedAttributes.keySet())
    {
      List<Attribute> attrList = suppressedAttributes.get(t);
      if (! t.isOperational())
      {
        userAttributes.put(t, attrList);
      }
    }
    Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
                     operationalAttrsCopy);
    if (processVirtual)
    {
      e.processVirtualAttributes(false);
    }
    return e;
  }
@@ -3172,14 +3215,15 @@
  /**
   * Performs a deep copy from the source map to the target map.  In
   * this case, the attributes in the list will be duplicates rather
   * than re-using the same reference.
   * than re-using the same reference.  Virtual attributes will not be
   * included when making the copy.
   *
   * @param  source  The source map from which to obtain the
   *                 information.
   * @param  target  The target map into which to place the copied
   *                 information.
   * @param  omitValues <CODE>true</CODE> if the values should be
   *                    omitted.
   * @param  target      The target map into which to place the
   *                     copied information.
   * @param  omitValues  Indicates whether to omit attribute values
   *                     when processing.
   */
  private void deepCopy(Map<AttributeType,List<Attribute>> source,
                        Map<AttributeType,List<Attribute>> target,
@@ -3193,12 +3237,20 @@
      for (Attribute a : sourceList)
      {
        if (a.isVirtual())
        {
          continue;
        }
        targetList.add(a.duplicate(omitValues));
      }
      if (! targetList.isEmpty())
      {
      target.put(t, targetList);
    }
  }
  }
@@ -3522,28 +3574,267 @@
   */
  public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
  {
    switch (scope)
    return dn.matchesBaseAndScope(baseDN, scope);
  }
  /**
   * 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()
    {
      case BASE_OBJECT:
        // The entry DN must equal the base DN.
        return baseDN.equals(dn);
    processVirtualAttributes(true);
  }
      case SINGLE_LEVEL:
        // The parent DN for this entry must equal the base DN.
        return baseDN.equals(dn.getParentDNInSuffix());
      case WHOLE_SUBTREE:
        // The base DN must be an ancestor of the entry DN.
        return baseDN.isAncestorOf(dn);
      case SUBORDINATE_SUBTREE:
        // The base DN must be an ancstor of the entry DN, but it
        // must not equal the entry DN.
        return ((! baseDN.equals(dn)) && baseDN.isAncestorOf(dn));
  /**
   * 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.
   *
   * @param  includeOperational  Indicates whether to include
   *                             operational attributes.
   */
  public void processVirtualAttributes(boolean includeOperational)
  {
    for (VirtualAttributeRule rule :
         DirectoryServer.getVirtualAttributes(this))
    {
      AttributeType attributeType = rule.getAttributeType();
      if (attributeType.isOperational() && (! includeOperational))
      {
        continue;
      }
      default:
        // This is a scope that we don't recognize.
        return false;
      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(new VirtualAttribute(attributeType, this,
                                            rule));
          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 (rule.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(new VirtualAttribute(attributeType, this,
                                                rule));
              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(new VirtualAttribute(attributeType, this,
                                                rule));
              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 (rule.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(new VirtualAttribute(attributeType, this,
                                              rule));
            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(new VirtualAttribute(attributeType, this,
                                              rule));
            break;
        }
      }
    }
    virtualAttributeProcessingPerformed = true;
  }
  /**
   * Indicates whether virtual attribute processing has been performed
   * for this entry.
   *
   * @return  {@code true} if virtual attribute processing has been
   *          performed for this entry, or {@code false} if not.
   */
  public boolean virtualAttributeProcessingPerformed()
  {
    return virtualAttributeProcessingPerformed;
  }
  /**
   * Strips out all real attributes from this entry so that it only
   * contains virtual attributes.
   */
  public void stripRealAttributes()
  {
    // The objectClass attribute will always be a real attribute.
    objectClasses.clear();
    Iterator<Map.Entry<AttributeType,List<Attribute>>>
         attrListIterator = userAttributes.entrySet().iterator();
    while (attrListIterator.hasNext())
    {
      Map.Entry<AttributeType,List<Attribute>> mapEntry =
           attrListIterator.next();
      Iterator<Attribute> attrIterator =
           mapEntry.getValue().iterator();
      while (attrIterator.hasNext())
      {
        Attribute a = attrIterator.next();
        if (! a.isVirtual())
        {
          attrIterator.remove();
        }
      }
      if (mapEntry.getValue().isEmpty())
      {
        attrListIterator.remove();
      }
    }
    attrListIterator = operationalAttributes.entrySet().iterator();
    while (attrListIterator.hasNext())
    {
      Map.Entry<AttributeType,List<Attribute>> mapEntry =
           attrListIterator.next();
      Iterator<Attribute> attrIterator =
           mapEntry.getValue().iterator();
      while (attrIterator.hasNext())
      {
        Attribute a = attrIterator.next();
        if (! a.isVirtual())
        {
          attrIterator.remove();
        }
      }
      if (mapEntry.getValue().isEmpty())
      {
        attrListIterator.remove();
      }
    }
  }
  /**
   * Strips out all virtual attributes from this entry so that it only
   * contains real attributes.
   */
  public void stripVirtualAttributes()
  {
    Iterator<Map.Entry<AttributeType,List<Attribute>>>
         attrListIterator = userAttributes.entrySet().iterator();
    while (attrListIterator.hasNext())
    {
      Map.Entry<AttributeType,List<Attribute>> mapEntry =
           attrListIterator.next();
      Iterator<Attribute> attrIterator =
           mapEntry.getValue().iterator();
      while (attrIterator.hasNext())
      {
        Attribute a = attrIterator.next();
        if (a.isVirtual())
        {
          attrIterator.remove();
        }
      }
      if (mapEntry.getValue().isEmpty())
      {
        attrListIterator.remove();
      }
    }
    attrListIterator = operationalAttributes.entrySet().iterator();
    while (attrListIterator.hasNext())
    {
      Map.Entry<AttributeType,List<Attribute>> mapEntry =
           attrListIterator.next();
      Iterator<Attribute> attrIterator =
           mapEntry.getValue().iterator();
      while (attrIterator.hasNext())
      {
        Attribute a = attrIterator.next();
        if (a.isVirtual())
        {
          attrIterator.remove();
        }
      }
      if (mapEntry.getValue().isEmpty())
      {
        attrListIterator.remove();
      }
    }
  }
@@ -3742,6 +4033,12 @@
        List<Attribute> attrList = userAttributes.get(attrType);
        for (Attribute a : attrList)
        {
          if (a.isVirtual() &&
              (! exportConfig.includeVirtualAttributes()))
          {
            continue;
          }
          if (exportConfig.typesOnly())
          {
            StringBuilder attrName = new StringBuilder(a.getName());
@@ -3787,7 +4084,7 @@
    }
    // Finally, the set of operational attributes.
    // Next, the set of operational attributes.
    if (exportConfig.includeOperationalAttributes())
    {
      for (AttributeType attrType : operationalAttributes.keySet())
@@ -3798,6 +4095,12 @@
               operationalAttributes.get(attrType);
          for (Attribute a : attrList)
          {
            if (a.isVirtual() &&
                (! exportConfig.includeVirtualAttributes()))
            {
              continue;
            }
            if (exportConfig.typesOnly())
            {
              StringBuilder attrName = new StringBuilder(a.getName());
@@ -3854,6 +4157,55 @@
      }
    }
    // If we are not supposed to include virtual attributes, then
    // write any attributes that may normally be suppressed by a
    // virtual attribute.
    if (! exportConfig.includeVirtualAttributes())
    {
      for (AttributeType t : suppressedAttributes.keySet())
      {
        if (exportConfig.includeAttribute(t))
        {
          for (Attribute a : suppressedAttributes.get(t))
          {
            if (exportConfig.typesOnly())
            {
              StringBuilder attrName = new StringBuilder(a.getName());
              for (String o : a.getOptions())
              {
                attrName.append(";");
                attrName.append(o);
              }
              attrName.append(":");
              writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
            }
            else
            {
              StringBuilder attrName = new StringBuilder(a.getName());
              for (String o : a.getOptions())
              {
                attrName.append(";");
                attrName.append(o);
              }
              for (AttributeValue v : a.getValues())
              {
                StringBuilder attrLine = new StringBuilder();
                attrLine.append(attrName);
                appendLDIFSeparatorAndValue(attrLine,
                                            v.getValueBytes());
                writeLDIFLine(attrLine, writer, wrapLines,
                              wrapColumn);
              }
            }
          }
        }
      }
    }
    // Make sure there is a blank line after the entry.
    writer.newLine();
opends/src/server/org/opends/server/types/LDIFExportConfig.java
@@ -54,9 +54,6 @@
 */
public class LDIFExportConfig
{
  // Indicates whether the data should be compressed as it is written.
  private boolean compressData;
@@ -75,6 +72,9 @@
  // export.
  private boolean includeOperationalAttributes;
  // Indicates whether to include virutal attributes in the export.
  private boolean includeVirtualAttributes;
  // Indicates whether to invoke LDIF export plugins on entries being
  // exported.
  private boolean invokeExportPlugins;
@@ -149,6 +149,7 @@
    hashData                     = false;
    includeObjectClasses         = true;
    includeOperationalAttributes = true;
    includeVirtualAttributes     = false;
    invokeExportPlugins          = false;
    signHash                     = false;
    typesOnly                    = false;
@@ -182,6 +183,7 @@
    hashData                     = false;
    includeObjectClasses         = true;
    includeOperationalAttributes = true;
    includeVirtualAttributes     = false;
    invokeExportPlugins          = false;
    signHash                     = false;
    typesOnly                    = false;
@@ -593,6 +595,36 @@
  /**
   * Indicates whether virtual attributes should be included in the
   * export.
   *
   * @return  {@code true} if virtual attributes should be included in
   *          the export, or {@code false} if not.
   */
  public boolean includeVirtualAttributes()
  {
    return includeVirtualAttributes;
  }
  /**
   * Specifies whether virtual attributes should be included in the
   * export.
   *
   * @param  includeVirtualAttributes  Specifies whether virtual
   *                                   attributes should be included
   *                                   in the export.
   */
  public void setIncludeVirtualAttributes(
                   boolean includeVirtualAttributes)
  {
    this.includeVirtualAttributes = includeVirtualAttributes;
  }
  /**
   * Retrieves the set of attributes that should be excluded from the
   * entries written to LDIF.  The set that is returned may be altered
   * by the caller.
opends/src/server/org/opends/server/types/VirtualAttribute.java
New file
@@ -0,0 +1,258 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.VirtualAttributeProvider;
/**
 * This class defines a virtual attribute, which is a special kind of
 * attribute whose values do not actually exist in persistent storage
 * but rather are computed or otherwise obtained dynamically.
 */
public class VirtualAttribute
       extends Attribute
{
  // The entry with which this virtual attribute is associated.
  private final Entry entry;
  // The virtual attribute provider for this virtual attribute.
  private final VirtualAttributeProvider<
                     ? extends VirtualAttributeCfg> provider;
  // The virtual attribute rule for this virtual attribute.
  private final VirtualAttributeRule rule;
  /**
   * Creates a new virtual attribute with the provided information.
   *
   * @param  attributeType  The attribute type for this virtual
   *                        attribute.
   * @param  entry          The entry in which this virtual attribute
   *                        exists.
* @param  rule           The virutal attribute rule that governs
   *                        the behavior of this virtual attribute.
   */
  public VirtualAttribute(AttributeType attributeType, Entry entry,
                          VirtualAttributeRule rule)
  {
    super(attributeType);
    this.entry = entry;
    this.rule  = rule;
    provider = rule.getProvider();
  }
  /**
   * Retrieves the entry in which this virtual attribute exists.
   *
   * @return  The entry in which this virtual attribute exists.
   */
  public Entry getEntry()
  {
    return entry;
  }
  /**
   * Retrieves the virtual attribute rule that governs the behavior of
   * this virtual attribute.
   *
   * @return  The virtual attribute rule that governs the behavior of
   *          this virtual attribute.
   */
  public VirtualAttributeRule getVirtualAttributeRule()
  {
    return rule;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashSet<AttributeValue> getValues()
  {
    return provider.getValues(entry, rule);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue()
  {
    return provider.hasValue(entry, rule);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasValue(AttributeValue value)
  {
    return provider.hasValue(entry, rule, value);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasAllValues(Collection<AttributeValue> values)
  {
    return provider.hasAllValues(entry, rule, values);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean hasAnyValue(Collection<AttributeValue> values)
  {
    return provider.hasAnyValue(entry, rule, values);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult matchesSubstring(ByteString subInitial,
                                          List<ByteString> subAny,
                                          ByteString subFinal)
  {
    return provider.matchesSubstring(entry, rule, subInitial, subAny,
                                     subFinal);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult greaterThanOrEqualTo(AttributeValue value)
  {
    return provider.greaterThanOrEqualTo(entry, rule, value);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult lessThanOrEqualTo(AttributeValue value)
  {
    return provider.lessThanOrEqualTo(entry, rule, value);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult approximatelyEqualTo(AttributeValue value)
  {
    return provider.approximatelyEqualTo(entry, rule, value);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isVirtual()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public Attribute duplicate(boolean omitValues)
  {
    return new VirtualAttribute(getAttributeType(), entry, rule);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void toString(StringBuilder buffer)
  {
    buffer.append("VirtualAttribute(");
    buffer.append(getAttributeType().getNameOrOID());
    buffer.append(", {");
    boolean firstValue = true;
    for (AttributeValue value : getValues())
    {
      if (! firstValue)
      {
        buffer.append(", ");
      }
      value.toString(buffer);
      firstValue = false;
    }
    buffer.append("})");
  }
}
opends/src/server/org/opends/server/types/VirtualAttributeRule.java
New file
@@ -0,0 +1,408 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.Iterator;
import java.util.Set;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.admin.std.server.VirtualAttributeCfg;
import org.opends.server.api.Group;
import org.opends.server.api.VirtualAttributeProvider;
import org.opends.server.core.DirectoryServer;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.Validator.*;
/**
 * This class defines a virtual attribute rule, which associates a
 * virtual attribute provider with its associated configuration,
 * including the attribute type for which the values should be
 * generated; the base DN(s), group DN(s), and search filter(s) that
 * should be used to identify which entries should have the virtual
 * attribute, and how conflicts between real and virtual values should
 * be handled.
 */
public class VirtualAttributeRule
{
  // The attribute type for which the values should be generated.
  private final AttributeType attributeType;
  // The set of base DNs for branches that are eligible to have this
  // virtual attribute.
  private final Set<DN> baseDNs;
  // The set of DNs for groups whose members are eligible to have this
  // virtual attribute.
  private final Set<DN> groupDNs;
  // The set of search filters for entries that are eligible to have
  // this virtual attribute.
  private final Set<SearchFilter> filters;
  // The virtual attribute provider used to generate the values.
  private final VirtualAttributeProvider<
                     ? extends VirtualAttributeCfg> provider;
  // The behavior that should be exhibited for entries that already
  // have real values for the target attribute.
  private final VirtualAttributeCfgDefn.ConflictBehavior
                     conflictBehavior;
  /**
   * Creates a new virtual attribute rule with the provided
   * information.
   *
   * @param  attributeType     The attribute type for which the values
   *                           should be generated.
   * @param  provider          The virtual attribute provider to use
   *                           to generate the values.
   * @param  baseDNs           The set of base DNs for branches that
   *                           are eligible to have this virtual
   *                           attribute.
   * @param  groupDNs          The set of DNs for groups whose members
   *                           are eligible to have this virtual
   *                           attribute.
   * @param  filters           The set of search filters for entries
   *                           that are eligible to have this virtual
   *                           attribute.
   * @param  conflictBehavior  The behavior that the server should
   *                           exhibit for entries that already have
   *                           one or more real values for the target
   *                           attribute.
   */
  public VirtualAttributeRule(AttributeType attributeType,
              VirtualAttributeProvider<? extends VirtualAttributeCfg>
                   provider,
              Set<DN> baseDNs, Set<DN> groupDNs,
              Set<SearchFilter> filters,
              VirtualAttributeCfgDefn.ConflictBehavior
                   conflictBehavior)
  {
    ensureNotNull(attributeType, provider, baseDNs, groupDNs);
    ensureNotNull(filters, conflictBehavior);
    this.attributeType    = attributeType;
    this.provider         = provider;
    this.baseDNs          = baseDNs;
    this.groupDNs         = groupDNs;
    this.filters          = filters;
    this.conflictBehavior = conflictBehavior;
  }
  /**
   * Retrieves the attribute type for which the values should be
   * generated.
   *
   * @return  The attribute type for which the values should be
   *          generated.
   */
  public AttributeType getAttributeType()
  {
    return attributeType;
  }
  /**
   *
   * Retrieves the virtual attribute provider used to generate the
   * values.
   *
   * @return  The virtual attribute provider to use to generate the
   *          values.
   */
  public VirtualAttributeProvider<? extends VirtualAttributeCfg>
              getProvider()
  {
    return provider;
  }
  /**
   * Retrieves the set of base DNs for branches that are eligible to
   * have this virtual attribute.
   *
   * @return  The set of base DNs for branches that are eligible to
   *          have this virtual attribute.
   */
  public Set<DN> getBaseDNs()
  {
    return baseDNs;
  }
  /**
   * Retrieves the set of DNs for groups whose members are eligible to
   * have this virtual attribute.
   *
   * @return  The set of DNs for groups whose members are eligible to
   *          have this virtual attribute.
   */
  public Set<DN> getGroupDNs()
  {
    return groupDNs;
  }
  /**
   * Retrieves the set of search filters for entries that are eligible
   * to have this virtual attribute.
   *
   * @return  The set of search filters for entries that are eligible
   *          to have this virtual attribute.
   */
  public Set<SearchFilter> getFilters()
  {
    return filters;
  }
  /**
   * Retrieves the behavior that the server should exhibit for entries
   * that already have one or more real values for the target
   * attribute.
   *
   * @return  The behavior that the server should exhibit for entries
   *          that already have one or more real values for the target
   *          attribute.
   */
  public VirtualAttributeCfgDefn.ConflictBehavior
              getConflictBehavior()
  {
    return conflictBehavior;
  }
  /**
   * Indicates whether this virtual attribute rule applies to the
   * provided entry, taking into account the eligibility requirements
   * defined in the rule.
   *
   * @param  entry  The entry for which to make the determination.
   *
   * @return  {@code true} if this virtual attribute rule may be used
   *          to generate values for the entry, or {@code false} if
   *          not.
   */
  public boolean appliesToEntry(Entry entry)
  {
    // We'll do this in order of expense so that the checks which are
    // potentially most expensive are done last.  First, check to see
    // if real values should override virtual ones and if so whether
    // the entry already has virtual values.
    if ((conflictBehavior == VirtualAttributeCfgDefn.ConflictBehavior.
                                  REAL_OVERRIDES_VIRTUAL) &&
        entry.hasAttribute(attributeType))
    {
      return false;
    }
    // If there are any base DNs defined, then the entry must be below
    // one of them.
    DN entryDN = entry.getDN();
    if (! baseDNs.isEmpty())
    {
      boolean found = false;
      for (DN dn : baseDNs)
      {
        if (entryDN.isDescendantOf(dn))
        {
          found = true;
          break;
        }
      }
      if (! found)
      {
        return false;
      }
    }
    // If there are any search filters defined, then the entry must
    // match one of them.
    if (! filters.isEmpty())
    {
      boolean found = false;
      for (SearchFilter filter : filters)
      {
        try
        {
          if (filter.matchesEntry(entry))
          {
            found = true;
            break;
          }
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
      if (! found)
      {
        return false;
      }
    }
    // If there are any group memberships defined, then the entry must
    // be a member of one of them.
    if (! groupDNs.isEmpty())
    {
      boolean found = false;
      for (DN dn : groupDNs)
      {
        try
        {
          Group group =
               DirectoryServer.getGroupManager().getGroupInstance(dn);
          if ((group != null) && group.isMember(entry))
          {
            found = true;
            break;
          }
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
      if (! found)
      {
        return false;
      }
    }
    // If we've gotten here, then the rule is applicable.
    return true;
  }
  /**
   * Retrieves a string representation of this virtual attribute rule.
   *
   * @return  A string representation of this virutal attribute rule.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this virtual attribute rule to
   * the provided buffer.
   *
   * @param  buffer  The buffer to which the information should be
   *                 written.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("VirtualAttributeRule(attrType=");
    buffer.append(attributeType.getNameOrOID());
    buffer.append(", providerDN=\"");
    buffer.append(provider.getClass().getName());
    buffer.append("\", baseDNs={");
    if (! baseDNs.isEmpty())
    {
      buffer.append("\"");
      Iterator<DN> iterator = baseDNs.iterator();
      buffer.append(iterator.next());
      while (iterator.hasNext())
      {
        buffer.append("\", \"");
        buffer.append(iterator.next());
      }
      buffer.append("\"");
    }
    buffer.append("}, groupDNs={");
    if (! groupDNs.isEmpty())
    {
      buffer.append("\"");
      Iterator<DN> iterator = groupDNs.iterator();
      buffer.append(iterator.next());
      while (iterator.hasNext())
      {
        buffer.append("\", \"");
        buffer.append(iterator.next());
      }
      buffer.append("\"");
    }
    buffer.append("}, filters={");
    if (! filters.isEmpty())
    {
      buffer.append("\"");
      Iterator<SearchFilter> iterator = filters.iterator();
      buffer.append(iterator.next());
      while (iterator.hasNext())
      {
        buffer.append("\", \"");
        buffer.append(iterator.next());
      }
      buffer.append("\"");
    }
    buffer.append("}, conflictBehavior=");
    buffer.append(conflictBehavior);
    buffer.append(")");
  }
}
opends/src/server/org/opends/server/util/LDIFWriter.java
@@ -58,8 +58,6 @@
 */
public final class LDIFWriter
{
  // FIXME -- Add support for generating a hash when writing the data.
  // FIXME -- Add support for signing the hash that is generated.
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1771,6 +1771,13 @@
  /**
   * The OID for the real attributes only control.
   */
  public static final String OID_REAL_ATTRS_ONLY = "2.16.840.1.113730.3.4.17";
  /**
   * The OID for the subtree delete control.
   */
  public static final String OID_SUBTREE_DELETE_CONTROL =
@@ -1803,6 +1810,14 @@
  /**
   * The OID for the virtual attributes only control.
   */
  public static final String OID_VIRTUAL_ATTRS_ONLY =
       "2.16.840.1.113730.3.4.19";
  /**
   * The block length in bytes used when generating an HMAC-MD5 digest.
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
@@ -136,7 +136,7 @@
    }
    // The add operation changes the attributes, so let's duplicate the entry.
    Entry duplicateEntry = testEntry.duplicate();
    Entry duplicateEntry = testEntry.duplicate(false);
    AddOperation addOperation =
         connection.processAdd(duplicateEntry.getDN(),
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java
New file
@@ -0,0 +1,1116 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringFactory;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.Control;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.VirtualAttributeRule;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * A set of test cases for the entryDN virtual attribute provider.
 */
public class EntryDNVirtualAttributeProviderTestCase
       extends ExtensionsTestCase
{
  // The attribute type for the entryDN attribute.
  private AttributeType entryDNType;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    entryDNType = DirectoryServer.getAttributeType("entrydn", false);
    assertNotNull(entryDNType);
  }
  /**
   * Retrieves a set of entry DNs for use in testing the entryDN virtual
   * attribute.
   *
   * @return  A set of entry DNs for use in testing the entryDN virtual
   *          attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "testEntryDNs")
  public Object[][] getTestEntryDNs()
         throws Exception
  {
    return new Object[][]
    {
      new Object[] { DN.decode("") },
      new Object[] { DN.decode("o=test") },
      new Object[] { DN.decode("dc=example,dc=com") },
      new Object[] { DN.decode("cn=config") },
      new Object[] { DN.decode("cn=schema") },
      new Object[] { DN.decode("cn=tasks") },
      new Object[] { DN.decode("cn=monitor") },
      new Object[] { DN.decode("cn=backups") }
    };
  }
  /**
   * Tests the {@code getEntry} method for the specified entry to ensure that
   * the entry returned includes the entryDN operational attribute with the
   * correct value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testGetEntry(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    Entry e = DirectoryServer.getEntry(entryDN);
    assertNotNull(e);
    assertTrue(e.hasAttribute(entryDNType));
    List<Attribute> attrList = e.getAttribute(entryDNType);
    assertNotNull(attrList);
    assertFalse(attrList.isEmpty());
    for (Attribute a : attrList)
    {
      assertTrue(a.hasValue());
      assertEquals(a.getValues().size(), 1);
      assertTrue(a.hasValue(new AttributeValue(entryDNType,
                                               entryDN.toNormalizedString())));
    }
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is not included when the list of attributes requested
   * is empty (defaulting to all user attributes).
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEmptyAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT, filter);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is not included when the list of requested attributes
   * is "1.1", meaning no attributes.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchNoAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("1.1");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is not included when all user attributes are
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllUserAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("*");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is included when all operational attributes are
   * requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllOperationalAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("+");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is included when the entryDN attribute is
   * specifically requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEntryDNAttr(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("entrydn");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is not included when it is not in the list of
   * attributes that is explicitly requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchExcludeEntryDNAttr(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("objectClass");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is included when the entryDN attribute is
   * specifically requested and the entryDN attribute is used in the search
   * filter with a matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEntryDNAttrInMatchingFilter(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(entryDN=" + entryDN.toString() +
                                             ")");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("entrydn");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * no entries are returned when the entryDN attribute is used in the search
   * filter with a non-matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEntryDNAttrInNonMatchingFilter(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(entryDN=cn=Not A Match)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("entrydn");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 0);
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is not included when the entryDN attribute is
   * specifically requested and the real attributes only control is included in
   * the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEntryDNAttrRealAttrsOnly(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("entrydn");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_REAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(entryDNType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the entryDN attribute is included when the entryDN attribute is
   * specifically requested and the virtual attributes only control is included
   * in the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEntryDNAttrVirtualAttrsOnly(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("entrydn");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_VIRTUAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(entryDNType));
  }
  /**
   * Tests the {@code isMultiValued} method.
   */
  @Test()
  public void testIsMultiValued()
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    assertFalse(provider.isMultiValued());
  }
  /**
   * Tests the {@code getValues} method for an entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testGetValues()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = provider.getValues(entry, rule);
    assertNotNull(values);
    assertEquals(values.size(), 1);
    assertTrue(values.contains(new AttributeValue(entryDNType, "o=test")));
  }
  /**
   * Tests the {@code hasValue} method variant that doesn't take a specific
   * value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValue()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertTrue(provider.hasValue(entry, rule));
  }
  /**
   * Tests the {@code hasValue} method variant that takes a specific value when
   * the provided value is a match.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasMatchingValue()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertTrue(provider.hasValue(entry, rule,
                                 new AttributeValue(entryDNType, "o=test")));
  }
  /**
   * Tests the {@code hasValue} method variant that takes a specific value when
   * the provided value is not a match.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasNonMatchingValue()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertFalse(provider.hasValue(entry, rule,
                     new AttributeValue(entryDNType, "o=not test")));
  }
  /**
   * Tests the {@code hasAnyValue} method with an empty set of values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueEmptySet()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertFalse(provider.hasAnyValue(entry, rule,
                                     Collections.<AttributeValue>emptySet()));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing only
   * the correct value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueOnlyCorrect()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    values.add(new AttributeValue(entryDNType, "o=test"));
    assertTrue(provider.hasAnyValue(entry, rule, values));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing only
   * an incorrect value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueOnlyIncorrect()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    values.add(new AttributeValue(entryDNType, "o=not test"));
    assertFalse(provider.hasAnyValue(entry, rule, values));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing the
   * correct value as well as multiple incorrect values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueIncludesCorrect()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
    values.add(new AttributeValue(entryDNType, "o=test"));
    values.add(new AttributeValue(entryDNType, "o=not test"));
    values.add(new AttributeValue(entryDNType, "o=not test either"));
    assertTrue(provider.hasAnyValue(entry, rule, values));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of multiple values, none of
   * which are correct.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueMissingCorrect()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
    values.add(new AttributeValue(entryDNType, "o=not test"));
    values.add(new AttributeValue(entryDNType, "o=not test either"));
    values.add(new AttributeValue(entryDNType, "o=still not test"));
    assertFalse(provider.hasAnyValue(entry, rule, values));
  }
  /**
   * Tests the {@code matchesSubstring} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMatchesSubstring()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedList<ByteString> subAny = new LinkedList<ByteString>();
    subAny.add(ByteStringFactory.create("="));
    assertEquals(provider.matchesSubstring(entry, rule, null, subAny, null),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Tests the {@code greaterThanOrEqualTo} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testGreaterThanOrEqualTo()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    AttributeValue value = new AttributeValue(entryDNType, "o=test2");
    assertEquals(provider.greaterThanOrEqualTo(entry, rule, value),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Tests the {@code lessThanOrEqualTo} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testLessThanOrEqualTo()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    AttributeValue value = new AttributeValue(entryDNType, "o=test2");
    assertEquals(provider.lessThanOrEqualTo(entry, rule, value),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Tests the {@code approximatelyEqualTo} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testApproximatelyEqualTo()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    AttributeValue value = new AttributeValue(entryDNType, "o=test2");
    assertEquals(provider.approximatelyEqualTo(entry, rule, value),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Retrieves a set of filters for use in testing searchability.  The returned
   * data will actually include three elements:
   * <OL>
   *   <LI>The string representation of the search filter to use</LI>
   *   <LI>An indication of whether it should be searchable</LI>
   *   <LI>An indication of whether a minimal o=test entry should match</LI>
   * </OL>
   *
   * @return  A set of filters for use in testing searchability.
   */
  @DataProvider(name = "testFilters")
  public Object[][] getTestFilters()
  {
    return new Object[][]
    {
      new Object[] { "(entryDN=o=test)", true, true },
      new Object[] { "(entryDN=o=not test)", true, false },
      new Object[] { "(o=test)", false, false },
      new Object[] { "(entryDN=*)", false, false },
      new Object[] { "(&(objectClass=*)(entryDN=o=test))", true, true },
      new Object[] { "(&(entryDN=o=test)(entryDN=o=not test))", true, false },
      new Object[] { "(|(objectClass=*)(entryDN=o=test))", false, false },
      new Object[] { "(|(entryDN=o=test)(entryDN=o=not test))", true, true },
      new Object[] { "(&(|(entryDN=o=test)(entryDN=o=not test))" +
                       "(&(objectClass=top)(|(objectClass=organization)" +
                                            "(objectClass=domain)))" +
                       "(|(o=test)(o=not test)))", true, true }
    };
  }
  /**
   * Tests the {@code isSearchable} method with the provided information.
   *
   * @param  filterString  The string representation of the search filter to use
   *                       for the test.
   * @param  isSearchable  Indicates whether a search with the given filter
   *                       should be considered searchable.
   * @param  shouldMatch   Indicates whether the provided filter should match
   *                       a minimal o=test entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testFilters")
  public void testIsSearchable(String filterString, boolean isSearchable,
                               boolean shouldMatch)
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    SearchFilter filter = SearchFilter.createFilterFromString(filterString);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), null,
                                     DN.decode("o=test"),
                                     SearchScope.WHOLE_SUBTREE,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, null, null);
    assertEquals(provider.isSearchable(rule, searchOperation), isSearchable);
  }
  /**
   * Tests the {@code processSearch} method with the provided information.
   *
   * @param  filterString  The string representation of the search filter to use
   *                       for the test.
   * @param  isSearchable  Indicates whether a search with the given filter
   *                       should be considered searchable.
   * @param  shouldMatch   Indicates whether the provided filter should match
   *                       a minimal o=test entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testFilters")
  public void testProcessSearch(String filterString, boolean isSearchable,
                                boolean shouldMatch)
         throws Exception
  {
    if (! isSearchable)
    {
      return;
    }
    TestCaseUtils.initializeTestBackend(true);
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(entryDNType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    SearchFilter filter = SearchFilter.createFilterFromString(filterString);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), null,
                                     DN.decode("o=test"),
                                     SearchScope.WHOLE_SUBTREE,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, null, null);
    provider.processSearch(rule, searchOperation);
    if (shouldMatch)
    {
      assertEquals(searchOperation.getSearchEntries().size(), 1);
    }
    else
    {
      assertEquals(searchOperation.getSearchEntries().size(), 0);
    }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java
New file
@@ -0,0 +1,1277 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringFactory;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.Control;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.VirtualAttributeRule;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * A set of test cases for the isMemberOf virtual attribute provider.
 */
public class IsMemberOfVirtualAttributeProviderTestCase
       extends ExtensionsTestCase
{
  // The attribute type for the isMemberOf attribute.
  private AttributeType isMemberOfType;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    isMemberOfType = DirectoryServer.getAttributeType("ismemberof", false);
    assertNotNull(isMemberOfType);
  }
  /**
   * Tests that the isMemberOf virtual attribute is properly generated for an
   * entry that is a member of a static group based on the member attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStaticGroupMembershipMember()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Static Group",
      "member: uid=test.user,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    assertNotNull(e);
    assertTrue(e.hasAttribute(isMemberOfType));
    for (Attribute a : e.getAttribute(isMemberOfType))
    {
      assertEquals(a.getValues().size(), 1);
      assertTrue(a.hasValue());
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test static group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=not a group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType, "invalid")));
    }
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test static group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests that the isMemberOf virtual attribute is properly generated for an
   * entry that is a member of a static group based on the uniqueMember
   * attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStaticGroupMembershipUniqueMember()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfUniqueNames",
      "cn: Test Static Group",
      "uniqueMember: uid=test.user,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    assertNotNull(e);
    assertTrue(e.hasAttribute(isMemberOfType));
    for (Attribute a : e.getAttribute(isMemberOfType))
    {
      assertEquals(a.getValues().size(), 1);
      assertTrue(a.hasValue());
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test static group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=not a group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType, "invalid")));
    }
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test static group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests that the isMemberOf virtual attribute is properly generated for an
   * entry that is a member of a dynamic group.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testDynamicGroupMembership()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Dynamic Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfURLs",
      "cn: Test Dynamic Group",
      "memberURL: ldap:///ou=People,o=test??sub?(sn=user)");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    assertNotNull(e);
    assertTrue(e.hasAttribute(isMemberOfType));
    for (Attribute a : e.getAttribute(isMemberOfType))
    {
      assertEquals(a.getValues().size(), 1);
      assertTrue(a.hasValue());
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                      "cn=test dynamic group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=not a group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType, "invalid")));
    }
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(
              DN.decode("cn=test dynamic group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests that the isMemberOf virtual attribute is properly generated for an
   * entry that is a member of multiple static groups.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMultipleStaticGroups()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: uid=test.user2,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user2",
      "givenName: Test",
      "sn: User2",
      "cn: Test User2",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Group 1,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 1",
      "member: uid=test.user,ou=People,o=test",
      "",
      "dn: cn=Test Group 2,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 2",
      "member: uid=test.user2,ou=People,o=test",
      "",
      "dn: cn=Test Group 3,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 3",
      "member: uid=test.user,ou=People,o=test",
      "member: uid=test.user2,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    assertNotNull(e);
    assertTrue(e.hasAttribute(isMemberOfType));
    for (Attribute a : e.getAttribute(isMemberOfType))
    {
      assertEquals(a.getValues().size(), 2);
      assertTrue(a.hasValue());
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test group 1,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=test group 2,ou=groups,o=test")));
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test group 3,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=not a group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType, "invalid")));
    }
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test group 1,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 2,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 3,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests that the isMemberOf virtual attribute is properly generated for an
   * entry that is a member of multiple static and dynamic groups.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMultipleGroups()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: uid=test.user2,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user2",
      "givenName: Test",
      "sn: User2",
      "cn: Test User2",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Group 1,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 1",
      "member: uid=test.user,ou=People,o=test",
      "",
      "dn: cn=Test Group 2,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 2",
      "member: uid=test.user2,ou=People,o=test",
      "",
      "dn: cn=Test Group 3,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 3",
      "member: uid=test.user,ou=People,o=test",
      "member: uid=test.user2,ou=People,o=test",
      "",
      "dn: cn=Test Group 4,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfURLs",
      "cn: Test Group 4",
      "memberURL: ldap:///o=test??sub?(uid=test.user)",
      "",
      "dn: cn=Test Group 5,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfURLs",
      "cn: Test Group 5",
      "memberURL: ldap:///o=test??sub?(uid=test.user1)",
      "",
      "dn: cn=Test Group 6,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfURLs",
      "cn: Test Group 6",
      "memberURL: ldap:///o=test??sub?(givenName=test)");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    assertNotNull(e);
    assertTrue(e.hasAttribute(isMemberOfType));
    for (Attribute a : e.getAttribute(isMemberOfType))
    {
      assertEquals(a.getValues().size(), 4);
      assertTrue(a.hasValue());
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test group 1,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=test group 2,ou=groups,o=test")));
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test group 3,ou=groups,o=test")));
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test group 4,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=test group 5,ou=groups,o=test")));
      assertTrue(a.hasValue(new AttributeValue(isMemberOfType,
                                     "cn=test group 6,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType,
                                      "cn=not a group,ou=groups,o=test")));
      assertFalse(a.hasValue(new AttributeValue(isMemberOfType, "invalid")));
    }
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test group 1,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 2,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 3,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 4,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 5,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 6,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the {@code isMultiValued} method.
   */
  @Test()
  public void testIsMultiValued()
  {
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    assertTrue(provider.isMultiValued());
  }
  /**
   * Tests the {@code hasAnyValue} method with an empty set of values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueEmptySet()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Static Group",
      "member: uid=test.user,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertFalse(provider.hasAnyValue(e, rule,
                                     Collections.<AttributeValue>emptySet()));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test static group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing only
   * the correct value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueOnlyCorrect()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Static Group",
      "member: uid=test.user,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    values.add(new AttributeValue(isMemberOfType,
                                  "cn=test static group,ou=groups,o=test"));
    assertTrue(provider.hasAnyValue(e, rule, values));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test static group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing only
   * an incorrect value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueOnlyIncorrect()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Static Group",
      "member: uid=test.user,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    values.add(new AttributeValue(isMemberOfType,
                                  "cn=test dynamic group,ou=groups,o=test"));
    assertFalse(provider.hasAnyValue(e, rule, values));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test static group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing the
   * correct value as well as multiple incorrect values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueIncludesCorrect()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Static Group",
      "member: uid=test.user,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    values.add(new AttributeValue(isMemberOfType,
                                  "cn=test static group,ou=groups,o=test"));
    values.add(new AttributeValue(isMemberOfType,
                                  "cn=test dynamic group,ou=groups,o=test"));
    assertTrue(provider.hasAnyValue(e, rule, values));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test static group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of multiple values, none of
   * which are correct.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueMissingCorrect()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Static Group,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Static Group",
      "member: uid=test.user,ou=People,o=test");
    Entry e =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
    values.add(new AttributeValue(isMemberOfType,
                                  "cn=test nonstatic group,ou=groups,o=test"));
    values.add(new AttributeValue(isMemberOfType,
                                  "cn=test dynamic group,ou=groups,o=test"));
    assertFalse(provider.hasAnyValue(e, rule, values));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test static group,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the {@code matchesSubstring} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMatchesSubstring()
         throws Exception
  {
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedList<ByteString> subAny = new LinkedList<ByteString>();
    subAny.add(ByteStringFactory.create("="));
    assertEquals(provider.matchesSubstring(entry, rule, null, subAny, null),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Tests the {@code greaterThanOrEqualTo} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testGreaterThanOrEqualTo()
         throws Exception
  {
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    AttributeValue value = new AttributeValue(isMemberOfType, "o=test2");
    assertEquals(provider.greaterThanOrEqualTo(entry, rule, value),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Tests the {@code lessThanOrEqualTo} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testLessThanOrEqualTo()
         throws Exception
  {
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    AttributeValue value = new AttributeValue(isMemberOfType, "o=test2");
    assertEquals(provider.lessThanOrEqualTo(entry, rule, value),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Tests the {@code approximatelyEqualTo} method to ensure that it returns a
   * result of "undefined".
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testApproximatelyEqualTo()
         throws Exception
  {
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    AttributeValue value = new AttributeValue(isMemberOfType, "o=test2");
    assertEquals(provider.approximatelyEqualTo(entry, rule, value),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Retrieves a set of filters for use in testing searchability.  The returned
   * data will actually include three elements:
   * <OL>
   *   <LI>The string representation of the search filter to use</LI>
   *   <LI>An indication of whether it should be searchable</LI>
   *   <LI>An indication of whether the uid=test.user,ou=People,o=test entry
   *       should match</LI>
   * </OL>
   *
   * @return  A set of filters for use in testing searchability.
   */
  @DataProvider(name = "testFilters")
  public Object[][] getTestFilters()
  {
    return new Object[][]
    {
      new Object[] { "(isMemberOf=*)", false, false },
      new Object[] { "(isMemberOf=cn*)", false, false },
      new Object[] { "(isMemberOf=invalid)", true, false },
      new Object[] { "(&(isMemberOf=invalid1)(isMemberOf=invalid2))",
                     true, false },
      new Object[] { "(isMemberOf>=cn=Test Group 1,ou=Groups,o=test)",
                     false, false },
      new Object[] { "(isMemberOf<=cn=Test Group 1,ou=Groups,o=test)",
                     false, false },
      new Object[] { "(isMemberOf~=cn=Test Group 1,ou=Groups,o=test)",
                     false, false },
      new Object[] { "(isMemberOf=cn=Test Group 1,ou=Groups,o=test)",
                     true, true },
      new Object[] { "(isMemberOf=cn=Test Group 2,ou=Groups,o=test)",
                     true, false },
      new Object[] { "(&(isMemberOf=cn=Test Group 1,ou=Groups,o=test)" +
                       "(givenName=test))",
                     true, true },
      new Object[] { "(&(isMemberOf=cn=Test Group 1,ou=Groups,o=test)" +
                       "(isMemberOf=invalid))",
                     true, false },
      new Object[] { "(&(isMemberOf=invalid)" +
                       "(isMemberOf=cn=Test Group 1,ou=Groups,o=test))",
                     true, false },
      new Object[] { "(&(isMemberOf=cn=Test Group 1,ou=Groups,o=test)" +
                       "(givenName=not test))",
                     true, false },
      new Object[] { "(&(isMemberOf=cn=Test Group 1,ou=Groups,o=test)" +
                       "(isMemberOf=cn=Test Group 2,ou=Groups,o=test))",
                     true, false },
      new Object[] { "(&(isMemberOf=cn=Test Group 1,ou=Groups,o=test)" +
                       "(isMemberOf=cn=Test Group 3,ou=Groups,o=test))",
                     true, true },
      new Object[] { "(&(isMemberOf=cn=Test Group 2,ou=Groups,o=test)" +
                       "(isMemberOf=cn=Test Group 4,ou=Groups,o=test))",
                     true, false },
      new Object[] { "(|(isMemberOf=cn=Test Group 1,ou=Groups,o=test)" +
                       "(isMemberOf=cn=Test Group 3,ou=Groups,o=test))",
                     false, false },
    };
  }
  /**
   * Tests the {@code isSearchable} method with the provided information.
   *
   * @param  filterString  The string representation of the search filter to use
   *                       for the test.
   * @param  isSearchable  Indicates whether a search with the given filter
   *                       should be considered searchable.
   * @param  shouldMatch   Indicates whether the provided filter should match
   *                       a minimal o=test entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testFilters")
  public void testIsSearchable(String filterString, boolean isSearchable,
                               boolean shouldMatch)
         throws Exception
  {
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    SearchFilter filter = SearchFilter.createFilterFromString(filterString);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), null,
                                     DN.decode("o=test"),
                                     SearchScope.WHOLE_SUBTREE,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, null, null);
    assertEquals(provider.isSearchable(rule, searchOperation), isSearchable);
  }
  /**
   * Tests the {@code processSearch} method with the provided information.
   *
   * @param  filterString  The string representation of the search filter to use
   *                       for the test.
   * @param  isSearchable  Indicates whether a search with the given filter
   *                       should be considered searchable.
   * @param  shouldMatch   Indicates whether the provided filter should match
   *                       a minimal o=test entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testFilters")
  public void testProcessSearch(String filterString, boolean isSearchable,
                                boolean shouldMatch)
         throws Exception
  {
    if (! isSearchable)
    {
      return;
    }
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: ou=People,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: People",
      "",
      "dn: uid=test.user,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: uid=test.user2,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user2",
      "givenName: Test",
      "sn: User2",
      "cn: Test User2",
      "userPassword: password",
      "",
      "dn: uid=test.user3,ou=People,o=test",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user3",
      "givenName: Test",
      "sn: User3",
      "cn: Test User3",
      "userPassword: password",
      "",
      "dn: ou=Groups,o=test",
      "objectClass: top",
      "objectClass: organizationalUnit",
      "ou: Groups",
      "",
      "dn: cn=Test Group 1,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 1",
      "member: uid=test.user,ou=People,o=test",
      "",
      "dn: cn=Test Group 2,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 2",
      "member: uid=test.user2,ou=People,o=test",
      "",
      "dn: cn=Test Group 3,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 3",
      "member: uid=test.user,ou=People,o=test",
      "member: uid=test.user2,ou=People,o=test",
      "",
      "dn: cn=Test Group 4,ou=Groups,o=test",
      "objectClass: top",
      "objectClass: groupOfNames",
      "cn: Test Group 4",
      "member: uid=test.user2,ou=People,o=test",
      "member: uid=test.user3,ou=People,o=test");
    Entry userEntry =
         DirectoryServer.getEntry(DN.decode("uid=test.user,ou=People,o=test"));
    assertNotNull(userEntry);
    IsMemberOfVirtualAttributeProvider provider =
         new IsMemberOfVirtualAttributeProvider();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(isMemberOfType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    SearchFilter filter = SearchFilter.createFilterFromString(filterString);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), null,
                                     DN.decode("o=test"),
                                     SearchScope.WHOLE_SUBTREE,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, null, null);
    provider.processSearch(rule, searchOperation);
    boolean matchFound = false;
    for (Entry e : searchOperation.getSearchEntries())
    {
      if (e.getDN().equals(userEntry.getDN()))
      {
        if (matchFound)
        {
          fail("Multiple matches found for the same user.");
        }
        else
        {
          matchFound = true;
        }
      }
    }
    assertEquals(matchFound, shouldMatch);
    DeleteOperation deleteOperation =
         conn.processDelete(DN.decode("cn=test group 1,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 2,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 3,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    deleteOperation =
         conn.processDelete(DN.decode("cn=test group 4,ou=groups,o=test"));
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProviderTestCase.java
New file
@@ -0,0 +1,848 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringFactory;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.Control;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.types.VirtualAttributeRule;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
/**
 * A set of test cases for the subschemaSubentry virtual attribute provider.
 */
public class SubschemaSubentryVirtualAttributeProviderTestCase
       extends ExtensionsTestCase
{
  // The attribute type for the subschemaSubentry attribute.
  private AttributeType subschemaSubentryType;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    subschemaSubentryType =
         DirectoryServer.getAttributeType("subschemasubentry", false);
    assertNotNull(subschemaSubentryType);
  }
  /**
   * Retrieves a set of entry DNs for use in testing the subschemaSubentry
   * virtual attribute.
   *
   * @return  A set of entry DNs for use in testing the subschemaSubentry
   *          virtual attribute.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "testEntryDNs")
  public Object[][] getTestEntryDNs()
         throws Exception
  {
    return new Object[][]
    {
      new Object[] { DN.decode("") },
      new Object[] { DN.decode("o=test") },
      new Object[] { DN.decode("dc=example,dc=com") },
      new Object[] { DN.decode("cn=config") },
      new Object[] { DN.decode("cn=schema") },
      new Object[] { DN.decode("cn=tasks") },
      new Object[] { DN.decode("cn=monitor") },
      new Object[] { DN.decode("cn=backups") }
    };
  }
  /**
   * Tests the {@code getEntry} method for the specified entry to ensure that
   * the entry returned includes the subschemaSubentry operational attribute
   * with the correct value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testGetEntry(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    Entry e = DirectoryServer.getEntry(entryDN);
    assertNotNull(e);
    assertTrue(e.hasAttribute(subschemaSubentryType));
    List<Attribute> attrList = e.getAttribute(subschemaSubentryType);
    assertNotNull(attrList);
    assertFalse(attrList.isEmpty());
    for (Attribute a : attrList)
    {
      assertTrue(a.hasValue());
      assertEquals(a.getValues().size(), 1);
      assertTrue(a.hasValue(new AttributeValue(subschemaSubentryType,
                                               "cn=schema")));
    }
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is not included when the list of attributes
   * requested is empty (defaulting to all user attributes).
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchEmptyAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT, filter);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is not included when the list of requested
   * attributes is "1.1", meaning no attributes.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchNoAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("1.1");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is not included when all user attributes
   * are requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllUserAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("*");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is included when all operational attributes
   * are requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchAllOperationalAttrs(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("+");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is included when that attribute is
   * specifically requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchSubschemaSubentryAttr(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("subschemasubentry");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is not included when it is not in the list
   * of attributes that is explicitly requested.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchExcludeSubschemaSubentryAttr(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("objectClass");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is included when that attribute is
   * specifically requested and the subschemaSubentry attribute is used in the
   * search filter with a matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchSubschemaSubentryAttrInMatchingFilter(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(subschemaSubentry=cn=schema)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("subschemaSubentry");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * no entries are returned when the subschemaSubentry attribute is used in the
   * search filter with a non-matching value.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchSubschemaSubentryAttrInNonMatchingFilter(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(subschemaSubentry=cn=foo)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("subschemaSubentry");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         conn.processSearch(entryDN, SearchScope.BASE_OBJECT,
                            DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
                            filter, attrList);
    assertEquals(searchOperation.getSearchEntries().size(), 0);
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is not included when that attribute is
   * specifically requested and the real attributes only control is included in
   * the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchSubschemaSubentryAttrRealAttrsOnly(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("subschemaSubentry");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_REAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertFalse(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Performs an internal search to retrieve the specified entry, ensuring that
   * the subschemaSubentry attribute is included when that attribute is
   * specifically requested and the virtual attributes only control is included
   * in the request.
   *
   * @param  entryDN  The DN of the entry to retrieve and verify.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testEntryDNs")
  public void testSearchSubschemaSubentryAttrVirtualAttrsOnly(DN entryDN)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    SearchFilter filter =
         SearchFilter.createFilterFromString("(objectClass=*)");
    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
    attrList.add("subschemaSubentry");
    LinkedList<Control> requestControls = new LinkedList<Control>();
    requestControls.add(new Control(OID_VIRTUAL_ATTRS_ONLY, true));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOperation =
         new InternalSearchOperation(conn, conn.nextOperationID(),
                                     conn.nextMessageID(), requestControls,
                                     entryDN, SearchScope.BASE_OBJECT,
                                     DereferencePolicy.NEVER_DEREF_ALIASES, 0,
                                     0, false, filter, attrList, null);
    searchOperation.run();
    assertEquals(searchOperation.getSearchEntries().size(), 1);
    Entry e = searchOperation.getSearchEntries().get(0);
    assertNotNull(e);
    assertTrue(e.hasAttribute(subschemaSubentryType));
  }
  /**
   * Tests the {@code isMultiValued} method.
   */
  @Test()
  public void testIsMultiValued()
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    assertFalse(provider.isMultiValued());
  }
  /**
   * Tests the {@code getValues} method for an entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testGetValues()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = provider.getValues(entry, rule);
    assertNotNull(values);
    assertEquals(values.size(), 1);
    assertTrue(values.contains(new AttributeValue(subschemaSubentryType,
                                                  "cn=schema")));
  }
  /**
   * Tests the {@code hasValue} method variant that doesn't take a specific
   * value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValue()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertTrue(provider.hasValue(entry, rule));
  }
  /**
   * Tests the {@code hasValue} method variant that takes a specific value when
   * the provided value is a match.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasMatchingValue()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertTrue(provider.hasValue(entry, rule,
                                 new AttributeValue(subschemaSubentryType,
                                                    "cn=schema")));
  }
  /**
   * Tests the {@code hasValue} method variant that takes a specific value when
   * the provided value is not a match.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasNonMatchingValue()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertFalse(provider.hasValue(entry, rule,
                     new AttributeValue(subschemaSubentryType,
                                        "cn=not schema")));
  }
  /**
   * Tests the {@code hasAnyValue} method with an empty set of values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueEmptySet()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    assertFalse(provider.hasAnyValue(entry, rule,
                                     Collections.<AttributeValue>emptySet()));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing only
   * the correct value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueOnlyCorrect()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    values.add(new AttributeValue(subschemaSubentryType, "cn=schema"));
    assertTrue(provider.hasAnyValue(entry, rule, values));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing only
   * an incorrect value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueOnlyIncorrect()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    values.add(new AttributeValue(subschemaSubentryType, "cn=not schema"));
    assertFalse(provider.hasAnyValue(entry, rule, values));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of values containing the
   * correct value as well as multiple incorrect values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueIncludesCorrect()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
    values.add(new AttributeValue(subschemaSubentryType, "cn=schema"));
    values.add(new AttributeValue(subschemaSubentryType, "cn=not schema"));
    values.add(new AttributeValue(subschemaSubentryType,
                                  "cn=not schema either"));
    assertTrue(provider.hasAnyValue(entry, rule, values));
  }
  /**
   * Tests the {@code hasAnyValue} method with a set of multiple values, none of
   * which are correct.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testHasAnyValueMissingCorrect()
         throws Exception
  {
    SubschemaSubentryVirtualAttributeProvider provider =
         new SubschemaSubentryVirtualAttributeProvider();
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    entry.processVirtualAttributes();
    VirtualAttributeRule rule =
         new VirtualAttributeRule(subschemaSubentryType, provider,
                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
                  Collections.<SearchFilter>emptySet(),
                  VirtualAttributeCfgDefn.ConflictBehavior.
                       VIRTUAL_OVERRIDES_REAL);
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
    values.add(new AttributeValue(subschemaSubentryType, "cn=not schema"));
    values.add(new AttributeValue(subschemaSubentryType,
                                  "cn=not schema either"));
    values.add(new AttributeValue(subschemaSubentryType,
                                  "cn=still not schema"));
    assertFalse(provider.hasAnyValue(entry, rule, values));
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/interop/LazyDNTestCase.java
@@ -40,6 +40,7 @@
import org.opends.server.TestCaseUtils;
import org.opends.server.types.DN;
import org.opends.server.types.RDN;
import org.opends.server.types.SearchScope;
import static org.testng.Assert.*;
@@ -110,6 +111,10 @@
    sigs.add(new String[] { "isAncestorOf",
                            "boolean",
                            "org.opends.server.types.DN" });
    sigs.add(new String[] { "matchesBaseAndScope",
                            "boolean",
                            "org.opends.server.types.DN",
                            "org.opends.server.types.SearchScope" });
    sigs.add(new String[] { "equals",
                            "boolean",
                            "java.lang.Object" });
@@ -578,6 +583,40 @@
  /**
   * Tests the {@code matchesBaseAndScope} method with valid DN strings.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMatchesBaseAndScope()
         throws Exception
  {
    assertTrue(new LazyDN("").matchesBaseAndScope(DN.nullDN(),
                                                  SearchScope.BASE_OBJECT));
    assertTrue(new LazyDN("dc=example,dc=com").matchesBaseAndScope(
                    DN.decode("dc=example,dc=com"), SearchScope.BASE_OBJECT));
    assertTrue(new LazyDN("ou=People,dc=example,dc=com").matchesBaseAndScope(
                    DN.decode("dc=example,dc=com"), SearchScope.WHOLE_SUBTREE));
  }
  /**
   * Tests the {@code matchesBaseAndScope} method with an invalid DN string.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { RuntimeException.class })
  public void testMatchesBaseandScopeInvalid()
         throws Exception
  {
    new LazyDN("invalid").matchesBaseAndScope(DN.decode("dc=example,dc=com"),
                                              SearchScope.WHOLE_SUBTREE);
  }
  /**
   * Tests the {@code equals} method with valid DN strings.
   *
   * @throws  Exception  If an unexpected problem occurs.
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/ProtocolWindowTest.java
@@ -113,7 +113,7 @@
      assertTrue(checkChangelogQueueSize(CHANGELOG_QUEUE_SIZE));
      // Create an Entry (add operation) that will be later used in the test.
      Entry tmp = personEntry.duplicate();
      Entry tmp = personEntry.duplicate(false);
      AddOperation addOp = new AddOperation(connection,
          InternalClientConnection.nextOperationID(), InternalClientConnection
          .nextMessageID(), null, tmp.getDN(),
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java
@@ -121,7 +121,7 @@
       */
      // Create an Entry (add operation) that will be later used in the test.
      Entry tmp = personEntry.duplicate();
      Entry tmp = personEntry.duplicate(false);
      AddOperation addOp = new AddOperation(connection,
          InternalClientConnection.nextOperationID(), InternalClientConnection
          .nextMessageID(), null, tmp.getDN(),
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
@@ -436,7 +436,7 @@
      if (entry == null)
        return null;
      else
        return entry.duplicate();
        return entry.duplicate(true);
    }
    finally
    {
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
@@ -844,7 +844,7 @@
       */
      // Create an Entry (add operation)
      Entry tmp = personEntry.duplicate();
      Entry tmp = personEntry.duplicate(false);
      AddOperation addOp = new AddOperation(connection,
          InternalClientConnection.nextOperationID(), InternalClientConnection
          .nextMessageID(), null, tmp.getDN(),
opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeRuleTestCase.java
New file
@@ -0,0 +1,337 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.Collections;
import java.util.LinkedHashSet;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.
            VirtualAttributeCfgDefn.ConflictBehavior;
import org.opends.server.extensions.EntryDNVirtualAttributeProvider;
import org.opends.server.protocols.internal.InternalClientConnection;
import static org.testng.Assert.*;
/**
 * This class provides a set of test cases for virtual attribute rules, which
 * link a virtual attribute provider implementation with an attribute type and a
 * set of criteria for identifying the entries with which that provider should
 * be used.
 */
public class VirtualAttributeRuleTestCase
       extends TypesTestCase
{
  // The attribute type for the entryDN attribute.
  private AttributeType entryDNType;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    entryDNType = DirectoryConfig.getAttributeType("entrydn", false);
    assertNotNull(entryDNType);
  }
  /**
   * Retrieves a set of virtual attribute rules that may be used for testing
   * purposes.  The return data will also include a Boolean value indicating
   * whether the rule would apply to a minimal "o=test" entry.
   *
   * @return  A set of virtual attribute rules that may be used for testing
   *          purposes.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "testRules")
  public Object[][] getVirtualAttributeRules()
         throws Exception
  {
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    LinkedHashSet<DN> dnSet1 = new LinkedHashSet<DN>(1);
    dnSet1.add(DN.decode("o=test"));
    LinkedHashSet<DN> dnSet2 = new LinkedHashSet<DN>(1);
    dnSet2.add(DN.decode("dc=example,dc=com"));
    LinkedHashSet<DN> dnSet3 = new LinkedHashSet<DN>(2);
    dnSet3.add(DN.decode("o=test"));
    dnSet3.add(DN.decode("dc=example,dc=com"));
    LinkedHashSet<DN> groupSet1 = new LinkedHashSet<DN>(1);
    groupSet1.add(DN.decode("cn=Test Group,o=test"));
    LinkedHashSet<DN> groupSet2 = new LinkedHashSet<DN>(1);
    groupSet2.add(DN.decode("cn=Example Group,o=test"));
    LinkedHashSet<DN> groupSet3= new LinkedHashSet<DN>(2);
    groupSet3.add(DN.decode("cn=Test Group,o=test"));
    groupSet3.add(DN.decode("cn=Example Group,o=test"));
    LinkedHashSet<SearchFilter> filterSet1 = new LinkedHashSet<SearchFilter>(1);
    filterSet1.add(SearchFilter.createFilterFromString("(objectClass=*)"));
    LinkedHashSet<SearchFilter> filterSet2 = new LinkedHashSet<SearchFilter>(1);
    filterSet2.add(SearchFilter.createFilterFromString("(o=test)"));
    LinkedHashSet<SearchFilter> filterSet3 = new LinkedHashSet<SearchFilter>(1);
    filterSet3.add(SearchFilter.createFilterFromString("(foo=bar)"));
    LinkedHashSet<SearchFilter> filterSet4 = new LinkedHashSet<SearchFilter>(2);
    filterSet4.add(SearchFilter.createFilterFromString("(o=test)"));
    filterSet4.add(SearchFilter.createFilterFromString("(foo=bar)"));
    return new Object[][]
    {
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(),
                                 Collections.<DN>emptySet(),
                                 Collections.<SearchFilter>emptySet(),
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider, dnSet1,
                                 Collections.<DN>emptySet(),
                                 Collections.<SearchFilter>emptySet(),
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider, dnSet2,
                                 Collections.<DN>emptySet(),
                                 Collections.<SearchFilter>emptySet(),
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        false
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider, dnSet3,
                                 Collections.<DN>emptySet(),
                                 Collections.<SearchFilter>emptySet(),
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(), groupSet1,
                                 Collections.<SearchFilter>emptySet(),
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(), groupSet2,
                                 Collections.<SearchFilter>emptySet(),
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        false
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(), groupSet3,
                                 Collections.<SearchFilter>emptySet(),
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(),
                                 Collections.<DN>emptySet(), filterSet1,
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(),
                                 Collections.<DN>emptySet(), filterSet2,
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(),
                                 Collections.<DN>emptySet(), filterSet3,
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        false
      },
      new Object[]
      {
        new VirtualAttributeRule(entryDNType, provider,
                                 Collections.<DN>emptySet(),
                                 Collections.<DN>emptySet(), filterSet4,
                                 ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
    };
  }
  /**
   * Tests the various getter methods in the virtual attribute rule class.
   *
   * @param  rule            The rule for which to perform the test.
   * @param  appliesToEntry  Indicates whether the provided rule applies to a
   *                         minimal "o=test" entry.
   */
  @Test(dataProvider = "testRules")
  public void testGetters(VirtualAttributeRule rule, boolean appliesToEntry)
  {
    assertEquals(rule.getAttributeType(), entryDNType);
    assertEquals(rule.getProvider().getClass().getName(),
                 EntryDNVirtualAttributeProvider.class.getName());
    assertNotNull(rule.getBaseDNs());
    assertNotNull(rule.getGroupDNs());
    assertNotNull(rule.getFilters());
    assertNotNull(rule.getConflictBehavior());
  }
  /**
   * Tests the {@code appliesToEntry} method.
   *
   * @param  rule            The rule for which to perform the test.
   * @param  appliesToEntry  Indicates whether the provided rule applies to a
   *                         minimal "o=test" entry.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testRules")
  public void testAppliesToEntry(VirtualAttributeRule rule,
                                 boolean appliesToEntry)
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    addGroups();
    assertEquals(rule.appliesToEntry(
                      DirectoryConfig.getEntry(DN.decode("o=test"))),
                 appliesToEntry);
    removeGroups();
  }
  /**
   * Tests the {@code toString} method.
   *
   * @param  rule            The rule for which to perform the test.
   * @param  appliesToEntry  Indicates whether the provided rule applies to a
   *                         minimal "o=test" entry.
   */
  @Test(dataProvider = "testRules")
  public void testToString(VirtualAttributeRule rule, boolean appliesToEntry)
  {
    String ruleString = rule.toString();
    assertNotNull(ruleString);
    assertTrue(ruleString.length() > 0);
  }
  /**
   * Adds a group to the server in which the "o=test" entry is a member.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void addGroups()
          throws Exception
  {
    TestCaseUtils.addEntries(
      "dn: cn=Test Group,o=test",
      "objectClass: top",
      "objectClass: groupOfUniqueNames",
      "cn: Test Group",
      "uniqueMember: o=test",
      "",
      "dn: cn=Example Group,o=test",
      "objectClass: top",
      "objectClass: groupOfUniqueNames",
      "cn: Example Group",
      "uniqueMember: dc=example,dc=com");
  }
  /**
   * Removes the test group from the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private void removeGroups()
          throws Exception
  {
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    conn.processDelete(DN.decode("cn=Test Group,o=Test"));
    conn.processDelete(DN.decode("cn=Example Group,o=Test"));
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeTestCase.java
New file
@@ -0,0 +1,192 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.Collections;
import java.util.LinkedHashSet;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.
            VirtualAttributeCfgDefn.ConflictBehavior;
import org.opends.server.extensions.EntryDNVirtualAttributeProvider;
import org.opends.server.protocols.internal.InternalClientConnection;
import static org.testng.Assert.*;
/**
 * This class provides a set of test cases for virtual attributes.
 */
public class VirtualAttributeTestCase
       extends TypesTestCase
{
  // The attribute type for the entryDN attribute.
  private AttributeType entryDNType;
  // The virtual attribute instance that will be used for all the testing.
  private VirtualAttribute virtualAttribute;
  // The virutal attribute rule that will be used for the testing.
  private VirtualAttributeRule virtualAttributeRule;
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    entryDNType = DirectoryConfig.getAttributeType("entrydn", false);
    assertNotNull(entryDNType);
    EntryDNVirtualAttributeProvider provider =
         new EntryDNVirtualAttributeProvider();
    virtualAttributeRule = new VirtualAttributeRule(entryDNType, provider,
                                    Collections.<DN>emptySet(),
                                    Collections.<DN>emptySet(),
                                    Collections.<SearchFilter>emptySet(),
                                    ConflictBehavior.VIRTUAL_OVERRIDES_REAL);
    Entry entry = TestCaseUtils.makeEntry(
      "dn: o=test",
      "objectClass: top",
      "objectClass: organization",
      "o: test");
    virtualAttribute = new VirtualAttribute(entryDNType, entry,
                                            virtualAttributeRule);
  }
  /**
   * Tests the various getter methods for virtual attributes.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testGetters()
         throws Exception
  {
    assertNotNull(virtualAttribute.getEntry());
    assertEquals(virtualAttribute.getEntry().getDN(),
                 DN.decode("o=test"));
    assertEquals(virtualAttribute.getVirtualAttributeRule(),
                 virtualAttributeRule);
    assertTrue(virtualAttribute.isVirtual());
    assertTrue(virtualAttribute.duplicate(true).isVirtual());
    assertTrue(virtualAttribute.duplicate(false).isVirtual());
  }
  /**
   * Tests the various methods that interact with the virtual values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testValues()
         throws Exception
  {
    LinkedHashSet<AttributeValue> values = virtualAttribute.getValues();
    assertEquals(values.size(), 1);
    assertTrue(values.contains(new AttributeValue(entryDNType, "o=test")));
    assertTrue(virtualAttribute.hasValue());
    assertTrue(virtualAttribute.hasValue(new AttributeValue(entryDNType,
                                                            "o=test")));
    assertFalse(virtualAttribute.hasValue(new AttributeValue(entryDNType,
                                                             "o=not test")));
    LinkedHashSet<AttributeValue> testValues =
         new LinkedHashSet<AttributeValue>();
    testValues.add(new AttributeValue(entryDNType, "o=test"));
    assertTrue(virtualAttribute.hasAllValues(testValues));
    assertTrue(virtualAttribute.hasAnyValue(testValues));
    testValues.add(new AttributeValue(entryDNType, "o=not test"));
    assertFalse(virtualAttribute.hasAllValues(testValues));
    assertTrue(virtualAttribute.hasAnyValue(testValues));
  }
  /**
   * Tests the various methods that apply to different kinds of matching.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testMatching()
         throws Exception
  {
    assertEquals(virtualAttribute.matchesSubstring(
                      ByteStringFactory.create("o="), null,
                      ByteStringFactory.create("test")),
                 ConditionResult.UNDEFINED);
    AttributeValue assertionValue = new AttributeValue(entryDNType, "o=test");
    assertEquals(virtualAttribute.greaterThanOrEqualTo(assertionValue),
                 ConditionResult.UNDEFINED);
    assertEquals(virtualAttribute.lessThanOrEqualTo(assertionValue),
                 ConditionResult.UNDEFINED);
    assertEquals(virtualAttribute.approximatelyEqualTo(assertionValue),
                 ConditionResult.UNDEFINED);
  }
  /**
   * Tests the {@code toString} method.
   */
  @Test()
  public void testToString()
  {
    String vattrString = virtualAttribute.toString();
    assertNotNull(vattrString);
    assertTrue(vattrString.length() > 0);
  }
}