From 9971cf7ea019068c313db98499001cb1eb1f72ab Mon Sep 17 00:00:00 2001
From: sin <sin@localhost>
Date: Fri, 15 May 2009 14:38:45 +0000
Subject: [PATCH] issue 3961:Implement operational attribute 'governingstructurerule

---
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GoverningStructureRuleVirtualAttributeConfiguration.xml                          |   69 +
 opendj-sdk/opends/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProvider.java                                  |  214 +++++
 opendj-sdk/opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java                                      |    2 
 opendj-sdk/opends/src/messages/messages/extension.properties                                                                                  |    2 
 opendj-sdk/opends/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProvider.java                                 |  298 +++++++
 opendj-sdk/opends/resource/config/config.ldif                                                                                                 |   20 
 opendj-sdk/opends/resource/schema/00-core.ldif                                                                                                |    6 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProviderTestCase.java |  898 +++++++++++++++++++++
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/StructuralObjectClassVirtualAttributeConfiguration.xml                           |   69 +
 opendj-sdk/opends/resource/schema/02-config.ldif                                                                                              |   10 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProviderTestCase.java  |  870 +++++++++++++++++++++
 11 files changed, 2,456 insertions(+), 2 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index b19db2a..a6d685c 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -2394,6 +2394,26 @@
 ds-cfg-attribute-type: subschemaSubentry
 ds-cfg-conflict-behavior: virtual-overrides-real
 
+dn: cn=structuralObjectClass,cn=Virtual Attributes,cn=config
+objectClass: top
+objectClass: ds-cfg-virtual-attribute
+objectClass: ds-cfg-structural-object-class-virtual-attribute
+cn: structuralObjectClass
+ds-cfg-java-class: org.opends.server.extensions.StructuralObjectClassVirtualAttributeProvider
+ds-cfg-enabled: true
+ds-cfg-attribute-type: structuralObjectClass
+ds-cfg-conflict-behavior: virtual-overrides-real
+
+dn: cn=governingStructureRule,cn=Virtual Attributes,cn=config
+objectClass: top
+objectClass: ds-cfg-virtual-attribute
+objectClass: ds-cfg-governing-structure-rule-virtual-attribute
+cn: governingStructureRule
+ds-cfg-java-class: org.opends.server.extensions.GoverningStructureRuleVirtualAttributeProvider
+ds-cfg-enabled: true
+ds-cfg-attribute-type: governingStructureRule
+ds-cfg-conflict-behavior: virtual-overrides-real
+
 dn: cn=Virtual Static member,cn=Virtual Attributes,cn=config
 objectClass: top
 objectClass: ds-cfg-virtual-attribute
diff --git a/opendj-sdk/opends/resource/schema/00-core.ldif b/opendj-sdk/opends/resource/schema/00-core.ldif
index 8476378..97ced53 100644
--- a/opendj-sdk/opends/resource/schema/00-core.ldif
+++ b/opendj-sdk/opends/resource/schema/00-core.ldif
@@ -195,6 +195,12 @@
 attributeTypes: ( 2.5.21.8 NAME 'matchingRuleUse' EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 USAGE directoryOperation
   X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.9 NAME 'structuralObjectClass' EQUALITY objectIdentifierMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 SINGLE-VALUE NO-USER-MODIFICATION
+  USAGE directoryOperation X-ORIGIN 'RFC 4512' )
+attributeTypes: ( 2.5.21.10 NAME 'governingStructureRule' EQUALITY integerMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE NO-USER-MODIFICATION
+  USAGE directoryOperation X-ORIGIN 'RFC 4512' )
 attributeTypes: ( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation X-ORIGIN 'RFC 4512' )
 attributeTypes: ( 1.3.6.1.4.1.1466.101.120.6 NAME 'altServer'
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index 6f5adf7..cdea6f0 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/opends/resource/schema/02-config.ldif
@@ -4039,4 +4039,14 @@
   SUP ds-cfg-backend
   STRUCTURAL
   X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.201
+  NAME 'ds-cfg-structural-object-class-virtual-attribute'
+  SUP ds-cfg-virtual-attribute
+  STRUCTURAL
+  X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.202
+  NAME 'ds-cfg-governing-structure-rule-virtual-attribute'
+  SUP ds-cfg-virtual-attribute
+  STRUCTURAL
+  X-ORIGIN 'OpenDS Directory Server' )
 
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GoverningStructureRuleVirtualAttributeConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GoverningStructureRuleVirtualAttributeConfiguration.xml
new file mode 100644
index 0000000..7b85b64
--- /dev/null
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GoverningStructureRuleVirtualAttributeConfiguration.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ! CDDL HEADER START
+  !
+  ! The contents of this file are subject to the terms of the
+  ! Common Development and Distribution License, Version 1.0 only
+  ! (the "License").  You may not use this file except in compliance
+  ! with the License.
+  !
+  ! You can obtain a copy of the license at
+  ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
+  ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+  ! See the License for the specific language governing permissions
+  ! and limitations under the License.
+  !
+  ! When distributing Covered Code, include this CDDL HEADER in each
+  ! file and include the License file at
+  ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+  ! add the following below this CDDL HEADER, with the fields enclosed
+  ! by brackets "[]" replaced with your own identifying information:
+  !      Portions Copyright [yyyy] [name of copyright owner]
+  !
+  ! CDDL HEADER END
+  !
+  !
+  !      Copyright 2009 Sun Microsystems, Inc.
+  ! -->
+<adm:managed-object name="governing-structure-rule-virtual-attribute"
+  plural-name="governing-structure-rule-virtual-attributes"
+  package="org.opends.server.admin.std" extends="virtual-attribute"
+  xmlns:adm="http://www.opends.org/admin"
+  xmlns:ldap="http://www.opends.org/admin-ldap">
+  <adm:synopsis>
+    The
+    <adm:user-friendly-name />
+      generates a virtual attribute that specifies the DIT structure rule 
+      with the schema definitions in effect for the 
+      entry. This attribute is defined in RFC 4512.
+  </adm:synopsis>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>ds-cfg-governing-structure-rule-virtual-attribute</ldap:name>
+      <ldap:superior>ds-cfg-virtual-attribute</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property-override name="java-class" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.extensions.GoverningSturctureRuleVirtualAttributeProvider
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  <adm:property-override name="conflict-behavior" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>virtual-overrides-real</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  <adm:property-override name="attribute-type">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>governingStructureRule</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+</adm:managed-object>
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/StructuralObjectClassVirtualAttributeConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/StructuralObjectClassVirtualAttributeConfiguration.xml
new file mode 100644
index 0000000..5b3c862
--- /dev/null
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/StructuralObjectClassVirtualAttributeConfiguration.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ! CDDL HEADER START
+  !
+  ! The contents of this file are subject to the terms of the
+  ! Common Development and Distribution License, Version 1.0 only
+  ! (the "License").  You may not use this file except in compliance
+  ! with the License.
+  !
+  ! You can obtain a copy of the license at
+  ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
+  ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+  ! See the License for the specific language governing permissions
+  ! and limitations under the License.
+  !
+  ! When distributing Covered Code, include this CDDL HEADER in each
+  ! file and include the License file at
+  ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+  ! add the following below this CDDL HEADER, with the fields enclosed
+  ! by brackets "[]" replaced with your own identifying information:
+  !      Portions Copyright [yyyy] [name of copyright owner]
+  !
+  ! CDDL HEADER END
+  !
+  !
+  !      Copyright 2009 Sun Microsystems, Inc.
+  ! -->
+<adm:managed-object name="structural-object-class-virtual-attribute"
+  plural-name="structural-object-class-virtual-attributes"
+  package="org.opends.server.admin.std" extends="virtual-attribute"
+  xmlns:adm="http://www.opends.org/admin"
+  xmlns:ldap="http://www.opends.org/admin-ldap">
+  <adm:synopsis>
+    The
+    <adm:user-friendly-name />
+      generates a virtual attribute that specifies the structural object class
+      with the schema definitions in effect for the
+      entry. This attribute is defined in RFC 4512.
+  </adm:synopsis>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>ds-cfg-structural-object-class-virtual-attribute</ldap:name>
+      <ldap:superior>ds-cfg-virtual-attribute</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property-override name="java-class" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.extensions.StructuralObjectClassVirtualAttributeProvider
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  <adm:property-override name="conflict-behavior" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>virtual-overrides-real</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  <adm:property-override name="attribute-type">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>structuralObjectClass</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+</adm:managed-object>
diff --git a/opendj-sdk/opends/src/messages/messages/extension.properties b/opendj-sdk/opends/src/messages/messages/extension.properties
index 3461468..e63b8aa 100644
--- a/opendj-sdk/opends/src/messages/messages/extension.properties
+++ b/opendj-sdk/opends/src/messages/messages/extension.properties
@@ -1134,7 +1134,7 @@
 MILD_ERR_UNIQUECHARS_VALIDATOR_NOT_ENOUGH_UNIQUE_CHARS_458=The provided \
  password does not contain enough unique characters.  The minimum number of \
  unique characters that may appear in a user password is %d
-MILD_ERR_SUBSCHEMASUBENTRY_VATTR_NOT_SEARCHABLE_459=The %s attribute is not \
+MILD_ERR_VATTR_NOT_SEARCHABLE_459=The %s attribute is not \
  searchable and should not be included in otherwise unindexed search filters
 MILD_ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY_460=The provided \
  password was found in the server's dictionary
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProvider.java
new file mode 100644
index 0000000..2aad816
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProvider.java
@@ -0,0 +1,298 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.
+        GoverningStructureRuleVirtualAttributeCfg;
+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.*;
+
+import static org.opends.messages.ExtensionMessages.*;
+
+
+
+/**
+ * This class implements a virtual attribute provider that is meant to serve
+ * the governingStructuralRule operational attribute as described in RFC 4512.
+ */
+public class GoverningStructureRuleVirtualAttributeProvider  extends
+         VirtualAttributeProvider<GoverningStructureRuleVirtualAttributeCfg>
+{
+  /**
+   * Creates a new instance of this governingStructureRule virtual attribute
+   * provider.
+   */
+  public GoverningStructureRuleVirtualAttributeProvider()
+  {
+    super();
+
+    // All initialization should be performed in the
+    // initializeVirtualAttributeProvider method.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void initializeVirtualAttributeProvider(
+                      GoverningStructureRuleVirtualAttributeCfg configuration)
+         throws ConfigException, InitializationException
+  {
+    // No initialization is required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isMultiValued()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public Set<AttributeValue> getValues(Entry entry,
+                                       VirtualAttributeRule rule)
+  {
+    DITStructureRule ditRule = getDITStructureRule(entry);
+
+    if(ditRule !=null)
+    {
+      return Collections.singleton(AttributeValues.create(
+                  rule.getAttributeType(),
+                  String.valueOf(ditRule.getRuleID())));
+    }
+
+    return Collections.<AttributeValue>emptySet();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
+  {
+    return getDITStructureRule(entry)!=null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult matchesSubstring(Entry entry,
+                                          VirtualAttributeRule rule,
+                                          ByteString subInitial,
+                                          List<ByteString> subAny,
+                                          ByteString subFinal)
+  {
+    // DITStructureRule cannot be used in substring matching.
+    return ConditionResult.UNDEFINED;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult greaterThanOrEqualTo(Entry entry,
+                              VirtualAttributeRule rule,
+                              AttributeValue value)
+  {
+    // DITStructureRule cannot be used in ordering matching.
+    return ConditionResult.UNDEFINED;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult lessThanOrEqualTo(Entry entry,
+                              VirtualAttributeRule rule,
+                              AttributeValue value)
+  {
+    // DITStructureRule cannot be used in ordering matching.
+    return ConditionResult.UNDEFINED;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult approximatelyEqualTo(Entry entry,
+                              VirtualAttributeRule rule,
+                              AttributeValue value)
+  {
+    // DITStructureRule 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)
+  {
+    //Non-searchable for unindexed searches.
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void processSearch(VirtualAttributeRule rule,
+                            SearchOperation searchOperation)
+  {
+    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+
+    Message message = ERR_VATTR_NOT_SEARCHABLE.get(
+            rule.getAttributeType().getNameOrOID());
+    searchOperation.appendErrorMessage(message);
+  }
+
+
+
+  //Checks if the entry matches the nameform.
+  private boolean matchesNameForm(NameForm nameForm,
+                       AcceptRejectWarn structuralPolicy,
+                       Entry entry)
+  {
+    RDN rdn = entry.getDN().getRDN();
+    if (rdn != null)
+    {
+      // Make sure that all the required attributes are present.
+      for (AttributeType t : nameForm.getRequiredAttributes())
+      {
+        if (! rdn.hasAttributeType(t))
+        {
+          if (structuralPolicy == AcceptRejectWarn.REJECT)
+          {
+            return false;
+          }
+        }
+      }
+
+      // Make sure that all attributes in the RDN are allowed.
+      int numAVAs = rdn.getNumValues();
+      for (int i = 0; i < numAVAs; i++)
+      {
+        AttributeType t = rdn.getAttributeType(i);
+        if (! nameForm.isRequiredOrOptional(t))
+        {
+          if (structuralPolicy == AcceptRejectWarn.REJECT)
+          {
+            return false;
+          }
+        }
+       }
+     }
+    return true;
+  }
+
+
+
+  //Finds the appropriate DIT structure rule for an entry.
+  private DITStructureRule getDITStructureRule(Entry entry)
+  {
+    ObjectClass oc = entry.getStructuralObjectClass();
+    List<NameForm> listForms = DirectoryServer.getNameForm(oc);
+    NameForm nameForm = null;
+    DITStructureRule ditRule = null;
+    //We iterate over all the nameforms while creating the entry and
+    //select the first one that matches. Since the entry exists, the same
+    //algorithm should work fine to retrieve the nameform which was
+    //applied while creating the entry.
+    if(listForms != null)
+    {
+      boolean obsolete = true;
+      AcceptRejectWarn structuralPolicy =
+         DirectoryServer.getSingleStructuralObjectClassPolicy();
+      for(NameForm nf : listForms)
+      {
+        if(!nf.isObsolete())
+        {
+          obsolete = false;
+          if(matchesNameForm(nf,
+                  structuralPolicy, entry))
+          {
+           nameForm = nf;
+           break;
+          }
+        }
+      }
+      if( nameForm != null && !obsolete)
+      {
+        ditRule =
+                DirectoryServer.getDITStructureRule(nameForm);
+      }
+    }
+    return ditRule;
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProvider.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProvider.java
new file mode 100644
index 0000000..6a1568b
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProvider.java
@@ -0,0 +1,214 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.
+        StructuralObjectClassVirtualAttributeCfg;
+import org.opends.server.api.VirtualAttributeProvider;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.types.*;
+
+import static org.opends.messages.ExtensionMessages.*;
+
+
+
+/**
+ * This class implements a virtual attribute provider that is meant to serve
+ * the structuralObjectClass operational attribute as described in RFC 4512.
+ */
+public class StructuralObjectClassVirtualAttributeProvider
+     extends VirtualAttributeProvider<StructuralObjectClassVirtualAttributeCfg>
+{
+  /**
+   * Creates a new instance of this structuralObjectClass virtual attribute
+   * provider.
+   */
+  public StructuralObjectClassVirtualAttributeProvider()
+  {
+    super();
+
+    // All initialization should be performed in the
+    // initializeVirtualAttributeProvider method.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void initializeVirtualAttributeProvider(
+                    StructuralObjectClassVirtualAttributeCfg configuration)
+         throws ConfigException, InitializationException
+  {
+    // No initialization is required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isMultiValued()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public Set<AttributeValue> getValues(Entry entry,
+                                       VirtualAttributeRule rule)
+  {
+    AttributeValue value =
+        AttributeValues.create(rule.getAttributeType(),
+        entry.getStructuralObjectClass().getNameOrOID());
+    return Collections.singleton(value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
+  {
+    //A structural object class is always present in an entry.
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult matchesSubstring(Entry entry,
+                                          VirtualAttributeRule rule,
+                                          ByteString subInitial,
+                                          List<ByteString> subAny,
+                                          ByteString subFinal)
+  {
+    //Substring matching is not supported.
+    return ConditionResult.UNDEFINED;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult greaterThanOrEqualTo(Entry entry,
+                              VirtualAttributeRule rule,
+                              AttributeValue value)
+  {
+    // An object class can not be used for ordering.
+    return ConditionResult.UNDEFINED;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult lessThanOrEqualTo(Entry entry,
+                              VirtualAttributeRule rule,
+                              AttributeValue value)
+  {
+    // An object class can not be used for ordering.
+    return ConditionResult.UNDEFINED;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public ConditionResult approximatelyEqualTo(Entry entry,
+                              VirtualAttributeRule rule,
+                              AttributeValue value)
+  {
+    // An object class can not 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);
+
+    Message message = ERR_VATTR_NOT_SEARCHABLE.get(
+            rule.getAttributeType().getNameOrOID());
+    searchOperation.appendErrorMessage(message);
+  }
+}
+
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
index adf9955..d06d9e4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/SubschemaSubentryVirtualAttributeProvider.java
@@ -194,7 +194,7 @@
   {
     searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
 
-    Message message = ERR_SUBSCHEMASUBENTRY_VATTR_NOT_SEARCHABLE.get(
+    Message message = ERR_VATTR_NOT_SEARCHABLE.get(
             rule.getAttributeType().getNameOrOID());
     searchOperation.appendErrorMessage(message);
   }
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProviderTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..ef30b65
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/GoverningStructureRuleVirtualAttributeProviderTestCase.java
@@ -0,0 +1,898 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import static org.opends.server.util.ServerConstants.*;
+import static org.testng.Assert.*;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+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.protocols.ldap.LDAPControl;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.AttributeValues;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
+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 org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+
+/**
+ * A set of test cases for the governing structure rule virtual attribute
+ * provider.
+ */
+public class GoverningStructureRuleVirtualAttributeProviderTestCase
+       extends ExtensionsTestCase
+{
+  // The attribute type for the governingStructureRule attribute.
+  private AttributeType governingStructureRuleType;
+
+
+
+  /**
+   * Ensures that the Directory Server is running.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @BeforeClass()
+  public void startServer()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+
+    governingStructureRuleType =
+         DirectoryServer.getAttributeType("governingstructurerule", false);
+    assertNotNull(governingStructureRuleType);
+    int resultCode = TestCaseUtils.applyModifications(true,
+    "dn: cn=schema",
+    "changetype: modify",
+    "add: nameForms",
+    "nameForms: ( domainNameForm-oid NAME 'domainNameForm' OC domain MUST ( dc ) )",
+    "nameForms: ( organizationalNameForm-oid NAME 'organizationalNameForm' OC organization MUST ( o ) )",
+    "-",
+    "add: ditStructureRules",
+    "dITStructureRules: ( 21 NAME 'domainStructureRule' FORM domainNameForm )",
+    "dITStructureRules: ( 22 NAME 'organizationalStructureRule' FORM organizationalNameForm SUP 21 )"
+    );
+    assertTrue(resultCode == 0);
+  }
+
+
+
+  /**
+   * Ensures that the schema is cleaned up.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+ @AfterClass()
+ public void cleanup() throws Exception
+ {
+    int resultCode = TestCaseUtils.applyModifications(true,
+    "dn: cn=schema",
+    "changetype: modify",
+    "delete: ditStructureRules",
+    "dITStructureRules: ( 22 NAME 'organizationalStructureRule' FORM organizationalNameForm SUP 21 )",
+    "dITStructureRules: ( 21 NAME 'domainStructureRule' FORM domainNameForm )",
+    "-",
+    "delete: nameForms",
+    "nameForms: ( domainNameForm-oid NAME 'domainNameForm' OC domain MUST ( dc ) )",
+    "nameForms: ( organizationalNameForm-oid NAME 'organizationalNameForm' OC organization MUST ( o ) )"
+     );
+    assertTrue(resultCode == 0);
+ }
+  
+  
+  
+  /**
+   * Retrieves a set of entry DNs for use in testing the
+   * governingStructureRule virtual attribute.
+   *
+   * @return  A set of entry DNs for use in testing the governingStructureRule
+   *          virtual attribute.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @DataProvider(name = "testEntryDNs")
+  public Object[][] getTestEntryDNs()
+         throws Exception
+  {
+    return new Object[][]
+    {
+      new Object[] { DN.decode("o=test") },
+      new Object[] { DN.decode("dc=example,dc=com") }
+    };
+  }
+
+
+
+  /**
+   * Retrieves a set of entry DNs and corresponding governing structure rule
+   * ids for use in testing the governingStructureRule virtual attribute.
+   *
+   * @return  A set of entry DNs and id for use in testing the
+   *           governingStructureRule virtual attribute.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @DataProvider(name = "testDNRuleID")
+  public Object[][] getTestEntryDNRuleID()
+         throws Exception
+  {
+    return new Object[][] {
+        {DN.decode("o=test"), "22"},
+        {DN.decode("dc=example,dc=com"), "21"},
+    };
+  }
+
+
+
+  /**
+   * Tests the {@code getEntry} method for the specified entry to ensure that
+   * the entry returned includes the governingStructureRule operational
+   * attribute with the correct value.
+   *
+   * @param  entryDN  The DN of the entry to retrieve and verify.
+   * @param ruleId The rule id of the DITStructureRule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testDNRuleID")
+  public void testGetEntry(DN entryDN,String ruleId)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
+
+    Entry e = DirectoryServer.getEntry(entryDN);
+    assertNotNull(e);
+    assertTrue(e.hasAttribute(governingStructureRuleType));
+
+    List<Attribute> attrList = e.getAttribute(governingStructureRuleType);
+    assertNotNull(attrList);
+    assertFalse(attrList.isEmpty());
+    for (Attribute a : attrList)
+    {
+      assertTrue(!a.isEmpty());
+      assertEquals(a.size(), 1);
+      assertTrue(a.contains(AttributeValues.create(governingStructureRuleType,
+                              ruleId)));
+    }
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRule 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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRule 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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRule 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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRuleType 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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRule 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 testSearchGoverningStructureRulesAttr(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("governingStructureRule");
+
+    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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRule 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 testSearchExcludeGovStructRuleAttr(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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRule attribute is included when that attribute is
+   * specifically requested and the governingStructureRule 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 = "testDNRuleID")
+  public void testSearchGovStructRuleInMatchingFilter(DN entryDN,String oc)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
+
+    SearchFilter filter =
+         SearchFilter.createFilterFromString("governingstructurerule="+oc);
+    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
+    attrList.add("governingStructureRule");
+
+    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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * no entries are returned when the governingStructureRule 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 testSearchGovStructRuleAttrInNonMatchingFilter(DN entryDN)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
+
+    SearchFilter filter =
+         SearchFilter.createFilterFromString("(governingStructureRule=1)");
+    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
+    attrList.add("governingStructureRuleType");
+
+    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 governingStructureRule 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 testSearchGovStructRuleAttrRealAttrsOnly(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("governingStructureRuleType");
+
+    LinkedList<Control> requestControls = new LinkedList<Control>();
+    requestControls.add(new LDAPControl(OID_REAL_ATTRS_ONLY, true));
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+        new InternalSearchOperation(conn, InternalClientConnection
+            .nextOperationID(), InternalClientConnection
+            .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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the governingStructureRule 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 testSearchGovStructRuleAttrVirtualAttrsOnly(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("governingStructureRule");
+
+    LinkedList<Control> requestControls = new LinkedList<Control>();
+    requestControls.add(new LDAPControl(OID_VIRTUAL_ATTRS_ONLY, true));
+
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+        new InternalSearchOperation(conn, InternalClientConnection
+            .nextOperationID(), InternalClientConnection
+            .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(governingStructureRuleType));
+  }
+
+
+
+  /**
+   * Tests the {@code isMultiValued} method.
+   */
+  @Test()
+  public void testIsMultiValued()
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+    assertFalse(provider.isMultiValued());
+  }
+
+
+
+  /**
+   * Tests the {@code getValues} method for an entry.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testGetValues()
+         throws Exception
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectclass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    Set<AttributeValue> values = provider.getValues(entry, rule);
+    assertNotNull(values);
+    assertEquals(values.size(), 1);
+    assertTrue(values.contains(AttributeValues.create(governingStructureRuleType,
+                                  "22")));
+  }
+
+
+
+  /**
+   * 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
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, 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
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectclass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    assertTrue(provider.hasValue(entry, rule,
+        AttributeValues.create(governingStructureRuleType,"22")));
+  }
+
+
+
+  /**
+   * 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
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    assertFalse(provider.hasValue(entry, rule,
+        AttributeValues.create(governingStructureRuleType,
+                                        "1")));
+  }
+
+
+
+  /**
+   * Tests the {@code hasAnyValue} method with an empty set of values.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testHasAnyValueEmptySet()
+         throws Exception
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectclass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, 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
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
+    values.add(AttributeValues.create(governingStructureRuleType, "22"));
+
+    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
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectclass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
+    values.add(AttributeValues.create(governingStructureRuleType, "1"));
+
+    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
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
+    values.add(AttributeValues.create(governingStructureRuleType, "22"));
+    values.add(AttributeValues.create(governingStructureRuleType, "1"));
+    values.add(AttributeValues.create(governingStructureRuleType,"2"));
+
+    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
+  {
+    GoverningStructureRuleVirtualAttributeProvider provider =
+         new GoverningStructureRuleVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectclass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(governingStructureRuleType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
+    values.add(AttributeValues.create(governingStructureRuleType, "1"));
+    values.add(AttributeValues.create(governingStructureRuleType, "2"));
+    values.add(AttributeValues.create(governingStructureRuleType,"3"));
+
+    assertFalse(provider.hasAnyValue(entry, rule, values));
+  }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProviderTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..afb8571
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/StructuralObjectClassVirtualAttributeProviderTestCase.java
@@ -0,0 +1,870 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import static org.opends.server.util.ServerConstants.*;
+import static org.testng.Assert.*;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+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.protocols.ldap.LDAPControl;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.AttributeValues;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
+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 org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+
+/**
+ * A set of test cases for the structural object class virtual attribute
+ * provider.
+ */
+public class StructuralObjectClassVirtualAttributeProviderTestCase
+       extends ExtensionsTestCase
+{
+  // The attribute type for the structuralobjectclass attribute.
+  private AttributeType structuralObjectClassType;
+
+
+
+  /**
+   * Ensures that the Directory Server is running.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @BeforeClass()
+  public void startServer()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+
+    structuralObjectClassType =
+         DirectoryServer.getAttributeType("structuralobjectclass", false);
+    assertNotNull(structuralObjectClassType);
+  }
+
+
+
+  /**
+   * Retrieves a set of entry DNs for use in testing the
+   * structuralObjectClassType virtual attribute.
+   *
+   * @return  A set of entry DNs for use in testing the structuralobjectclass
+   *          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") }
+    };
+  }
+
+
+
+  /**
+   * Retrieves a set of entry DNs and corresponding structural object classes
+   * for use in testing the structuralObjectClassType virtual attribute.
+   *
+   * @return  A set of entry DNs and oc for use in testing the
+   *           structuralobjectclass virtual attribute.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @DataProvider(name = "testDNOC")
+  public Object[][] getTestEntryDNOC()
+         throws Exception
+  {
+    return new Object[][] {  
+        {DN.decode("o=test"), "structuralObjectClass=organization"},
+        {DN.decode("dc=example,dc=com"), "structuralObjectClass=domain"},
+    };
+  }
+  
+  
+  
+  /**
+   * Tests the {@code getEntry} method for the specified entry to ensure that
+   * the entry returned includes the structuralObjectClass 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(structuralObjectClassType));
+
+    List<Attribute> attrList = e.getAttribute(structuralObjectClassType);
+    assertNotNull(attrList);
+    assertFalse(attrList.isEmpty());
+    for (Attribute a : attrList)
+    {
+      assertTrue(!a.isEmpty());
+      assertEquals(a.size(), 1);
+      assertTrue(a.contains(AttributeValues.create(structuralObjectClassType,
+                              e.getStructuralObjectClass().getNameOrOID())));
+    }
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass 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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass 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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass 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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass 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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass 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 testSearchStructuralOCAttr(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("structuralobjectclass");
+
+    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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass 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 testSearchExcludeStructuralOCAttr(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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass attribute is included when that attribute is
+   * specifically requested and the structuralObjectClass 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 = "testDNOC")
+  public void testSearchStructuralOCAttrInMatchingFilter(DN entryDN,String oc)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
+
+    SearchFilter filter =
+         SearchFilter.createFilterFromString(oc);
+    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
+    attrList.add("structuralObjectClass");
+
+    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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * no entries are returned when the structuralObjectClass 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 testSearchStructuralOCAttrInNonMatchingFilter(DN entryDN)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
+
+    SearchFilter filter =
+         SearchFilter.createFilterFromString("(structuralObjectClass=abc)");
+    LinkedHashSet<String> attrList = new LinkedHashSet<String>(1);
+    attrList.add("structuralObjectClass");
+
+    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 structuralObjectClass 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 testSearchStructuralOCAttrRealAttrsOnly(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("structuralObjectClass");
+
+    LinkedList<Control> requestControls = new LinkedList<Control>();
+    requestControls.add(new LDAPControl(OID_REAL_ATTRS_ONLY, true));
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+        new InternalSearchOperation(conn, InternalClientConnection
+            .nextOperationID(), InternalClientConnection
+            .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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Performs an internal search to retrieve the specified entry, ensuring that
+   * the structuralObjectClass 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 testSearchStructuralOCAttrVirtualAttrsOnly(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("structuralObjectClass");
+
+    LinkedList<Control> requestControls = new LinkedList<Control>();
+    requestControls.add(new LDAPControl(OID_VIRTUAL_ATTRS_ONLY, true));
+
+    InternalClientConnection conn =
+        InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+        new InternalSearchOperation(conn, InternalClientConnection
+            .nextOperationID(), InternalClientConnection
+            .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(structuralObjectClassType));
+  }
+
+
+
+  /**
+   * Tests the {@code isMultiValued} method.
+   */
+  @Test()
+  public void testIsMultiValued()
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+    assertFalse(provider.isMultiValued());
+  }
+
+
+
+  /**
+   * Tests the {@code getValues} method for an entry.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testGetValues()
+         throws Exception
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    Set<AttributeValue> values = provider.getValues(entry, rule);
+    assertNotNull(values);
+    assertEquals(values.size(), 1);
+    assertTrue(values.contains(AttributeValues.create(structuralObjectClassType,
+                                  entry.getStructuralObjectClass().getNameOrOID())));
+  }
+
+
+
+  /**
+   * 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
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, 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
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    assertTrue(provider.hasValue(entry, rule,
+        AttributeValues.create(structuralObjectClassType,
+                          entry.getStructuralObjectClass().getNameOrOID())));
+  }
+
+
+
+  /**
+   * 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
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    assertFalse(provider.hasValue(entry, rule,
+        AttributeValues.create(structuralObjectClassType,
+                                        "inetorgperson")));
+  }
+
+
+
+  /**
+   * Tests the {@code hasAnyValue} method with an empty set of values.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testHasAnyValueEmptySet()
+         throws Exception
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, 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
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
+    values.add(AttributeValues.create(structuralObjectClassType, "organization"));
+
+    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
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
+    values.add(AttributeValues.create(structuralObjectClassType, "inetorgperson"));
+
+    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
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
+    values.add(AttributeValues.create(structuralObjectClassType, "organization"));
+    values.add(AttributeValues.create(structuralObjectClassType, "inetorgperson"));
+    values.add(AttributeValues.create(structuralObjectClassType,
+                                  "top"));
+
+    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
+  {
+    StructuralObjectClassVirtualAttributeProvider provider =
+         new StructuralObjectClassVirtualAttributeProvider();
+
+    Entry entry = TestCaseUtils.makeEntry(
+      "dn: o=test",
+      "objectClass: top",
+      "objectClass: organization",
+      "o: test");
+    entry.processVirtualAttributes();
+
+    VirtualAttributeRule rule =
+         new VirtualAttributeRule(structuralObjectClassType, provider,
+                  Collections.<DN>emptySet(), Collections.<DN>emptySet(),
+                  Collections.<SearchFilter>emptySet(),
+                  VirtualAttributeCfgDefn.ConflictBehavior.
+                       VIRTUAL_OVERRIDES_REAL);
+
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(3);
+    values.add(AttributeValues.create(structuralObjectClassType, "inetorgperson"));
+    values.add(AttributeValues.create(structuralObjectClassType,
+                                  "top"));
+    values.add(AttributeValues.create(structuralObjectClassType,
+                                  "domain"));
+
+    assertFalse(provider.hasAnyValue(entry, rule, values));
+  }
+}
+

--
Gitblit v1.10.0