From 09dc131d36b33e58f728316c3d445dfc3b865be4 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Tue, 03 Apr 2007 18:52:11 +0000
Subject: [PATCH] Add initial support for a virtual attribute subsystem, and implement a few different kinds of virtual attributes.  This commit addresses the following issues:

---
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java                      |   10 
 opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java                                                                 |    2 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java           | 1116 ++++++++
 opendj-sdk/opends/src/server/org/opends/server/types/LDIFExportConfig.java                                                               |   38 
 opendj-sdk/opends/src/server/org/opends/server/config/ConfigEntry.java                                                                   |    2 
 opendj-sdk/opends/src/server/org/opends/server/api/Group.java                                                                            |    3 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java                                                               |    8 
 opendj-sdk/opends/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java                                        |  437 +++
 opendj-sdk/opends/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java                                           |  399 ++
 opendj-sdk/opends/src/server/org/opends/server/backends/BackupBackend.java                                                               |   15 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java                                                               |    6 
 opendj-sdk/opends/resource/config/config.ldif                                                                                            |   28 
 opendj-sdk/opends/src/server/org/opends/server/types/DN.java                                                                             |   58 
 opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java                                                         |   22 
 opendj-sdk/opends/src/server/org/opends/server/types/Entry.java                                                                          |  444 ++
 opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttribute.java                                                               |  258 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java                          |    2 
 opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java                                                                 |   15 
 opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttributeRule.java                                                           |  408 ++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java                                 |    2 
 opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java                                                              |   63 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeTestCase.java                               |  192 +
 opendj-sdk/opends/src/server/org/opends/server/backends/RootDSEBackend.java                                                              |   35 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/interop/LazyDNTestCase.java                                       |   39 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeRuleTestCase.java                           |  337 ++
 opendj-sdk/opends/src/server/org/opends/server/core/VirtualAttributeConfigManager.java                                                   |  578 ++++
 opendj-sdk/opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java                                 |  210 +
 opendj-sdk/opends/resource/schema/00-core.ldif                                                                                           |    4 
 opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java                                                            |   13 
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java                                                                 |    6 
 opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java                                                               |    5 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java                                   |    2 
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VirtualAttributeConfiguration.xml                                           |  183 +
 opendj-sdk/opends/src/server/org/opends/server/api/VirtualAttributeProvider.java                                                         |  637 ++++
 opendj-sdk/opends/src/server/org/opends/server/backends/MonitorBackend.java                                                              |   17 
 opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java                                                          |   15 
 opendj-sdk/opends/src/server/org/opends/server/backends/MemoryBackend.java                                                               |   31 
 opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java                                                          |   14 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java        | 1277 +++++++++
 opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java                                                                 |    9 
 opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java                                                                 |  125 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/ProtocolWindowTest.java                           |    2 
 opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java                                                                    |    2 
 opendj-sdk/opends/src/server/org/opends/server/util/LDIFWriter.java                                                                      |    2 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java                                                                |   12 
 opendj-sdk/opends/src/server/org/opends/server/interop/LazyDN.java                                                                       |   23 
 /dev/null                                                                                                                                |  141 -
 opendj-sdk/opends/src/server/org/opends/server/tools/LDAPToolUtils.java                                                                  |   10 
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml                                                       |    8 
 opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java                                          |   50 
 opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java                                                                      |   54 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProviderTestCase.java |  848 ++++++
 opendj-sdk/opends/resource/schema/02-config.ldif                                                                                         |   28 
 opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java                                                                 |   32 
 54 files changed, 7,907 insertions(+), 370 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index 6c192c1..5cf8c5b 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/resource/schema/00-core.ldif b/opendj-sdk/opends/resource/schema/00-core.ldif
index 295178c..cbe3be8 100644
--- a/opendj-sdk/opends/resource/schema/00-core.ldif
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index b5791ca..a54ec37 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/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' )
 
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml
index 72785fe..d3a9ab8 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml
+++ b/opendj-sdk/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>
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VirtualAttributeConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VirtualAttributeConfiguration.xml
new file mode 100644
index 0000000..c0a1eb6
--- /dev/null
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/VirtualAttributeConfiguration.xml
@@ -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>
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
index 4eaf510..75740c4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/ClientConnection.java
+++ b/opendj-sdk/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;
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/Group.java b/opendj-sdk/opends/src/server/org/opends/server/api/Group.java
index 4c89272..25e9e38 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/Group.java
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/VirtualAttribute.java b/opendj-sdk/opends/src/server/org/opends/server/api/VirtualAttribute.java
deleted file mode 100644
index 5104aec..0000000
--- a/opendj-sdk/opends/src/server/org/opends/server/api/VirtualAttribute.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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 org.opends.server.config.ConfigEntry;
-import org.opends.server.config.ConfigException;
-import org.opends.server.core.SearchOperation;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.ByteString;
-import org.opends.server.types.Entry;
-import org.opends.server.types.InitializationException;
-import org.opends.server.types.SearchResultEntry;
-
-
-
-/**
- * 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.
- */
-public abstract class VirtualAttribute
-{
-  /**
-   * Initializes this virtual attribute based on the information in
-   * the provided configuration entry.
-   *
-   * @param  configEntry  The configuration entry that contains the
-   *                      information to use to initialize this
-   *                      virtual attribute.
-   *
-   * @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 initializeVirtualAttribute(
-                            ConfigEntry configEntry)
-         throws ConfigException, InitializationException;
-
-
-
-  /**
-   * Retrieves the name of this virtual attribute.
-   *
-   * @return  The name of this virtual attribute.
-   */
-  public abstract String getName();
-
-
-
-  /**
-   * Indicates whether the provided entry matches the given
-   * attribute-value assertion.
-   *
-   * @param  entry           The entry for which to make the
-   *                         determination.
-   * @param  attributeType   The attribute type for which to make the
-   *                         determination.
-   * @param  assertionValue  The value for which to make the
-   *                         determination.
-   *
-   * @return  <CODE>true</CODE> if the given entry matches the
-   *          provided assertion details, or <CODE>false</CODE> if
-   *          not.
-   */
-  public abstract boolean entryMatches(Entry entry,
-                               AttributeType attributeType,
-                               ByteString assertionValue);
-
-
-
-  /**
-   * Updates the provided entry if appropriate to include one or more
-   * values for this virtual attribute.  The entry may be left
-   * unaltered if it is not one that should contain this virtual
-   * attribute.  This method may alter real attributes if appropriate
-   * as well.
-   *
-   * @param  entry            The entry to be updated if necessary.
-   * @param  searchOperation  The search operation with which the
-   *                          entry is associated.
-   */
-  public abstract void updateEntry(SearchResultEntry entry,
-                                   SearchOperation searchOperation);
-
-
-
-  /**
-   * Indicates whether this attribute may be included in search
-   * filters as part of the criteria for locating entries.
-   *
-   * @return  <CODE>true</CODE> if this attribute may be included in
-   *          search filters, or <CODE>false</CODE> if not.
-   */
-  public abstract boolean isSearchable();
-
-
-
-  /**
-   * Processes the provided search operation in which the search
-   * criteria includes an operation targeted at this virtual
-   * attribute.  This method will 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  searchOperation  The search operation to be processed.
-   */
-  public abstract void processSearch(SearchOperation searchOperation);
-}
-
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/VirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/api/VirtualAttributeProvider.java
new file mode 100644
index 0000000..bb25ed4
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/VirtualAttributeProvider.java
@@ -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);
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/BackupBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/BackupBackend.java
index 41dc517..b0d888a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/BackupBackend.java
+++ b/opendj-sdk/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;
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/MemoryBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/MemoryBackend.java
index 07c7345..3dda52d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/MemoryBackend.java
+++ b/opendj-sdk/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);
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/MonitorBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/MonitorBackend.java
index 169b6c8..ef39963 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/MonitorBackend.java
+++ b/opendj-sdk/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,
-                     monitorOperationalAttrs);
+    Entry e = new Entry(baseMonitorDN, monitorClasses, monitorUserAttrs,
+                        monitorOperationalAttrs);
+    e.processVirtualAttributes();
+    return e;
   }
 
 
@@ -706,8 +703,10 @@
       }
     }
 
-    return new Entry(entryDN, monitorClasses, attrMap,
-                     new HashMap<AttributeType,List<Attribute>>(0));
+    Entry e = new Entry(entryDN, monitorClasses, attrMap,
+                        new HashMap<AttributeType,List<Attribute>>(0));
+    e.processVirtualAttributes();
+    return e;
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/RootDSEBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/RootDSEBackend.java
index 9c7daf1..142f6e9 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/RootDSEBackend.java
+++ b/opendj-sdk/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,
-                     dseOperationalAttrs);
+    Entry e = new Entry(rootDSEDN, dseObjectClasses, dseUserAttrs,
+                        dseOperationalAttrs);
+    e.processVirtualAttributes();
+    return e;
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
index 7a8034f..042bafe 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opendj-sdk/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;
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
index 5f2559d..be17587 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
+++ b/opendj-sdk/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;
   }
 
   /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java
index 7883c28..c5e141f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java
+++ b/opendj-sdk/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());
       }
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java
index e6e42ca..aa3ba12 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskBackend.java
+++ b/opendj-sdk/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;
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java b/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
index 3294a46..4e0ecc4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/task/TaskScheduler.java
+++ b/opendj-sdk/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))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigEntry.java b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigEntry.java
index fe3396d..85ff2fb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigEntry.java
+++ b/opendj-sdk/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);
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
index 25e398f..ef72f64 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/AddOperation.java
+++ b/opendj-sdk/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()))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java
index bfb74fc..32ac395 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DeleteOperation.java
+++ b/opendj-sdk/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()))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
index 43fb847..205e04c 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opendj-sdk/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.
    *
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java
index 6766475..3b58b7d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyDNOperation.java
+++ b/opendj-sdk/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()))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
index d424bdd..1c9ea1d 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opendj-sdk/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()))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
index 45f54d3..7d4f2f7 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/opendj-sdk/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())
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/VirtualAttributeConfigManager.java b/opendj-sdk/opends/src/server/org/opends/server/core/VirtualAttributeConfigManager.java
new file mode 100644
index 0000000..fc4ce4f
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/VirtualAttributeConfigManager.java
@@ -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);
+    }
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
index e0b0de5..4a159c6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opendj-sdk/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))
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java
new file mode 100644
index 0000000..92f9af0
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java
@@ -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;
+    }
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java
new file mode 100644
index 0000000..b4ced1b
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java
@@ -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;
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java
new file mode 100644
index 0000000..4472529
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java
@@ -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());
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/interop/LazyDN.java b/opendj-sdk/opends/src/server/org/opends/server/interop/LazyDN.java
index 173a9a9..774eafc 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/interop/LazyDN.java
+++ b/opendj-sdk/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
   {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
index 7eef8e8..751e211 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ConfigMessages.java
+++ b/opendj-sdk/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.");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
index bc856f8..7866cc2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
+++ b/opendj-sdk/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.");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
index cfdf825..6c3467b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalClientConnection.java
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPToolUtils.java b/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPToolUtils.java
index 81b8c43..ca40b69 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/LDAPToolUtils.java
+++ b/opendj-sdk/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)
     {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java b/opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java
index d649300..029f43a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java
+++ b/opendj-sdk/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);
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/DN.java b/opendj-sdk/opends/src/server/org/opends/server/types/DN.java
index ad60aff..fc6e7fd 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/DN.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java b/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
index 9dcf168..214405a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
+++ b/opendj-sdk/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;
 
@@ -137,8 +134,13 @@
                Map<AttributeType,List<Attribute>>
                     operationalAttributes)
   {
-    attachment = null;
-    schema     = DirectoryServer.getSchema();
+    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,
-                     operationalAttrsCopy);
+    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,
-                     operationalAttrsCopy);
+    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  source      The source map from which to obtain the
+   *                     information.
+   * @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,10 +3237,18 @@
 
       for (Attribute a : sourceList)
       {
+        if (a.isVirtual())
+        {
+          continue;
+        }
+
         targetList.add(a.duplicate(omitValues));
       }
 
-      target.put(t, targetList);
+      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()
+  {
+    processVirtualAttributes(true);
+  }
+
+
+
+  /**
+   * 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))
     {
-      case BASE_OBJECT:
-        // The entry DN must equal the base DN.
-        return baseDN.equals(dn);
+      AttributeType attributeType = rule.getAttributeType();
+      if (attributeType.isOperational() && (! includeOperational))
+      {
+        continue;
+      }
 
-      case SINGLE_LEVEL:
-        // The parent DN for this entry must equal the base DN.
-        return baseDN.equals(dn.getParentDNInSuffix());
+      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;
+          }
 
-      case WHOLE_SUBTREE:
-        // The base DN must be an ancestor of the entry DN.
-        return baseDN.isAncestorOf(dn);
+          // 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 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));
+            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;
 
-      default:
-        // This is a scope that we don't recognize.
-        return false;
+            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();
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/LDIFExportConfig.java b/opendj-sdk/opends/src/server/org/opends/server/types/LDIFExportConfig.java
index a5b6938..793d175 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/LDIFExportConfig.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttribute.java b/opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttribute.java
new file mode 100644
index 0000000..0fec185
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttribute.java
@@ -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("})");
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttributeRule.java b/opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttributeRule.java
new file mode 100644
index 0000000..1344853
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/VirtualAttributeRule.java
@@ -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(")");
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/LDIFWriter.java b/opendj-sdk/opends/src/server/org/opends/server/util/LDIFWriter.java
index 05cc73c..f504e5a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/LDIFWriter.java
+++ b/opendj-sdk/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.
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
index e5c3468..990b2b6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
index ce63a78..6b9a6f0 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
+++ b/opendj-sdk/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(),
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..dd6ff2f
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/EntryDNVirtualAttributeProviderTestCase.java
@@ -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);
+    }
+  }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..5487dbb
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/IsMemberOfVirtualAttributeProviderTestCase.java
@@ -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);
+  }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProviderTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..810c485
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProviderTestCase.java
@@ -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));
+  }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/interop/LazyDNTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/interop/LazyDNTestCase.java
index b739017..7145bae 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/interop/LazyDNTestCase.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/ProtocolWindowTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/ProtocolWindowTest.java
index bc39d1a..1478b9c 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/ProtocolWindowTest.java
+++ b/opendj-sdk/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(),
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java
index 9a797fd..44b8188 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java
+++ b/opendj-sdk/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(),
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
index f89e5a2..fd4e449 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
@@ -410,11 +410,11 @@
     while ((count> 0) && (found != exist))
     {
       Thread.sleep(200);
-  
+
       found = DirectoryServer.entryExists(dn);
       count--;
     }
-  
+
     Lock lock = null;
     for (int i=0; i < 3; i++)
     {
@@ -424,19 +424,19 @@
         break;
       }
     }
-  
+
     if (lock == null)
     {
       throw new Exception("could not lock entry " + dn);
     }
-  
+
     try
     {
       Entry entry = DirectoryServer.getEntry(dn);
       if (entry == null)
         return null;
       else
-        return entry.duplicate();
+        return entry.duplicate(true);
     }
     finally
     {
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
index c151dea..a681cc3 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
+++ b/opendj-sdk/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(),
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeRuleTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeRuleTestCase.java
new file mode 100644
index 0000000..dbec7cb
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeRuleTestCase.java
@@ -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"));
+  }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeTestCase.java
new file mode 100644
index 0000000..409a713
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/VirtualAttributeTestCase.java
@@ -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);
+  }
+}
+

--
Gitblit v1.10.0