From d6a18c0ea456c2f132c2301b37c80fe7e3e3a276 Mon Sep 17 00:00:00 2001
From: coulbeck <coulbeck@localhost>
Date: Thu, 12 Oct 2006 16:20:20 +0000
Subject: [PATCH] Fixes for the following issues, reviewed by neil_a_wilson.

---
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java |  189 +++++++++++++++++++-
 opends/src/server/org/opends/server/types/Attribute.java                                      |   39 +++
 opends/src/server/org/opends/server/types/Entry.java                                          |  141 +++++++++++++-
 opends/src/server/org/opends/server/core/SearchOperation.java                                 |  168 ++++--------------
 4 files changed, 373 insertions(+), 164 deletions(-)

diff --git a/opends/src/server/org/opends/server/core/SearchOperation.java b/opends/src/server/org/opends/server/core/SearchOperation.java
index 16620e2..d0feac0 100644
--- a/opends/src/server/org/opends/server/core/SearchOperation.java
+++ b/opends/src/server/org/opends/server/core/SearchOperation.java
@@ -932,14 +932,6 @@
             List<Attribute> ocList = new ArrayList<Attribute>(1);
             ocList.add(new Attribute(ocType));
             entryToReturn.putAttribute(ocType, ocList);
-
-            // Next, iterate through all the user attributes and include them.
-            for (AttributeType t : entry.getUserAttributes().keySet())
-            {
-              List<Attribute> attrList = new ArrayList<Attribute>(1);
-              attrList.add(new Attribute(t));
-              entryToReturn.putAttribute(t, attrList);
-            }
           }
           else
           {
@@ -954,13 +946,14 @@
               // We cannot get this exception because the object classes have
               // already been validated in the entry they came from.
             }
+          }
 
-
-            // Next iterate through all the user attributes and include them.
-            for (AttributeType t : entry.getUserAttributes().keySet())
-            {
-              entryToReturn.putAttribute(t, entry.duplicateUserAttribute(t));
-            }
+          // Next iterate through all the user attributes and include them.
+          for (AttributeType t : entry.getUserAttributes().keySet())
+          {
+            List<Attribute> attrList =
+                 entry.duplicateUserAttribute(t, null, typesOnly);
+            entryToReturn.putAttribute(t, attrList);
           }
 
           continue;
@@ -971,17 +964,9 @@
           // attributes should be returned.
           for (AttributeType t : entry.getOperationalAttributes().keySet())
           {
-            if (typesOnly)
-            {
-              List<Attribute> attrList = new ArrayList<Attribute>(1);
-              attrList.add(new Attribute(t));
-              entryToReturn.putAttribute(t, attrList);
-            }
-            else
-            {
-              entryToReturn.putAttribute(t,
-                                 entry.duplicateOperationalAttribute(t));
-            }
+            List<Attribute> attrList =
+                 entry.duplicateOperationalAttribute(t, null, typesOnly);
+            entryToReturn.putAttribute(t, attrList);
           }
 
           continue;
@@ -1020,53 +1005,15 @@
           {
             if (t.hasNameOrOID(lowerName))
             {
-              if ((options == null) || options.isEmpty())
+              List<Attribute> attrList =
+                   entry.duplicateUserAttribute(t, options, typesOnly);
+              if (attrList != null)
               {
-                if (typesOnly)
-                {
-                  List<Attribute> attrList = new ArrayList<Attribute>(1);
-                  attrList.add(new Attribute(t));
-                  entryToReturn.putAttribute(t, attrList);
-                }
-                else
-                {
-                  entryToReturn.putAttribute(t,
-                                             entry.duplicateUserAttribute(t));
-                }
+                entryToReturn.putAttribute(t, attrList);
 
                 added = true;
                 break;
               }
-              else
-              {
-                List<Attribute> attrList = entry.duplicateUserAttribute(t);
-                List<Attribute> includeAttrs =
-                     new ArrayList<Attribute>(attrList.size());
-                for (Attribute a : attrList)
-                {
-                  if (a.hasOptions(options))
-                  {
-                    includeAttrs.add(a);
-                  }
-                }
-
-                if (! includeAttrs.isEmpty())
-                {
-                  if (typesOnly)
-                  {
-                    attrList = new ArrayList<Attribute>(1);
-                    attrList.add(new Attribute(t));
-                    entryToReturn.putAttribute(t, attrList);
-                  }
-                  else
-                  {
-                    entryToReturn.putAttribute(t, includeAttrs);
-                  }
-
-                  added = true;
-                  break;
-                }
-              }
             }
           }
 
@@ -1079,87 +1026,48 @@
           {
             if (t.hasNameOrOID(lowerName))
             {
-              if ((options == null) || options.isEmpty())
+              List<Attribute> attrList =
+                   entry.duplicateOperationalAttribute(t, options, typesOnly);
+              if (attrList != null)
               {
-                if (typesOnly)
-                {
-                  List<Attribute> attrList = new ArrayList<Attribute>(1);
-                  attrList.add(new Attribute(t));
-                  entryToReturn.putAttribute(t, attrList);
-                }
-                else
-                {
-                  entryToReturn.putAttribute(t,
-                                     entry.duplicateOperationalAttribute(t));
-                }
+                entryToReturn.putAttribute(t, attrList);
 
-                added = true;
                 break;
               }
-              else
-              {
-                List<Attribute> attrList =
-                     entry.duplicateOperationalAttribute(t);
-                List<Attribute> includeAttrs =
-                     new ArrayList<Attribute>(attrList.size());
-                for (Attribute a : attrList)
-                {
-                  if (a.hasOptions(options))
-                  {
-                    includeAttrs.add(a);
-                  }
-                }
-
-                if (! includeAttrs.isEmpty())
-                {
-                  if (typesOnly)
-                  {
-                    attrList = new ArrayList<Attribute>(1);
-                    attrList.add(new Attribute(t));
-                    entryToReturn.putAttribute(t, attrList);
-                  }
-                  else
-                  {
-                    entryToReturn.putAttribute(t, includeAttrs);
-                  }
-
-                  added = true;
-                  break;
-                }
-              }
             }
           }
         }
         else
         {
-          if (attrType.isObjectClassType() && !typesOnly)
-          {
-            Attribute ocAttr = entry.getObjectClassAttribute();
-            try
+          if (attrType.isObjectClassType()) {
+            if (typesOnly)
             {
-              entryToReturn.setObjectClasses(ocAttr.getValues());
+              AttributeType ocType =
+                   DirectoryServer.getObjectClassAttributeType();
+              List<Attribute> ocList = new ArrayList<Attribute>(1);
+              ocList.add(new Attribute(ocType));
+              entryToReturn.putAttribute(ocType, ocList);
             }
-            catch (DirectoryException e)
+            else
             {
-              // We cannot get this exception because the object classes have
-              // already been validated in the entry they came from.
+              List<Attribute> attrList = new ArrayList<Attribute>(1);
+              attrList.add(entry.getObjectClassAttribute());
+              entryToReturn.putAttribute(attrType, attrList);
             }
           }
           else
           {
-            List<Attribute> attrList = entry.getAttribute(attrType, options);
+            List<Attribute> attrList =
+                 entry.duplicateOperationalAttribute(attrType, options,
+                                                     typesOnly);
+            if (attrList == null)
+            {
+              attrList = entry.duplicateUserAttribute(attrType, options,
+                                                      typesOnly);
+            }
             if (attrList != null)
             {
-              if (typesOnly)
-              {
-                attrList = new ArrayList<Attribute>(1);
-                attrList.add(new Attribute(attrType));
-                entryToReturn.putAttribute(attrType, attrList);
-              }
-              else
-              {
-                entryToReturn.putAttribute(attrType, attrList);
-              }
+              entryToReturn.putAttribute(attrType, attrList);
             }
           }
         }
diff --git a/opends/src/server/org/opends/server/types/Attribute.java b/opends/src/server/org/opends/server/types/Attribute.java
index e7f0801..fec73c5 100644
--- a/opends/src/server/org/opends/server/types/Attribute.java
+++ b/opends/src/server/org/opends/server/types/Attribute.java
@@ -836,6 +836,25 @@
   {
     assert debugEnter(CLASS_NAME, "duplicate");
 
+    return duplicate(false);
+  }
+
+
+  /**
+   * Creates a duplicate of this attribute that can be modified
+   * without impacting this attribute.
+   *
+   * @param omitValues <CODE>true</CODE> if the values should be
+   *        omitted.
+   *
+   * @return  A duplicate of this attribute that can be modified
+   *          without impacting this attribute.
+   */
+  public Attribute duplicate(boolean omitValues)
+  {
+    assert debugEnter(CLASS_NAME, "duplicate",
+                      String.valueOf(omitValues));
+
     LinkedHashSet<String> optionsCopy =
          new LinkedHashSet<String>(options.size());
     for (String s : options)
@@ -843,19 +862,25 @@
       optionsCopy.add(s);
     }
 
-    LinkedHashSet<AttributeValue> valuesCopy =
-         new LinkedHashSet<AttributeValue>(values.size());
-    for (AttributeValue v : values)
+    if (omitValues)
     {
-      valuesCopy.add(v);
+      return new Attribute(attributeType, name, optionsCopy, null);
     }
+    else
+    {
+      LinkedHashSet<AttributeValue> valuesCopy =
+           new LinkedHashSet<AttributeValue>(values.size());
+      for (AttributeValue v : values)
+      {
+        valuesCopy.add(v);
+      }
 
-    return new Attribute(attributeType, name, optionsCopy,
-                         valuesCopy);
+      return new Attribute(attributeType, name, optionsCopy,
+                           valuesCopy);
+    }
   }
 
 
-
   /**
    * Indicates whether the provided object is an attribute that is
    * equal to this attribute.  It will be considered equal if the
diff --git a/opends/src/server/org/opends/server/types/Entry.java b/opends/src/server/org/opends/server/types/Entry.java
index b33b3f1..f2d9f54 100644
--- a/opends/src/server/org/opends/server/types/Entry.java
+++ b/opends/src/server/org/opends/server/types/Entry.java
@@ -1146,6 +1146,122 @@
 
 
   /**
+   * Makes a copy of attributes matching the specified options.
+   *
+   * @param  attrList       The attributes to be copied.
+   * @param  options        The set of attribute options to include in
+   *                        matching elements.
+   * @param  omitValues     <CODE>true</CODE> if the values are to be
+   *                        omitted.
+   *
+   * @return  A copy of the attributes matching the specified options,
+   *          or <CODE>null</CODE> if there is no such attribute with
+   *          the specified set of options.
+   */
+  private static List<Attribute> duplicateAttribute(
+       List<Attribute> attrList,
+       Set<String> options,
+       boolean omitValues)
+  {
+    assert debugEnter(CLASS_NAME, "duplicateAttribute",
+                      String.valueOf(attrList),
+                      String.valueOf(options),
+                      String.valueOf(omitValues));
+
+    if (attrList == null)
+    {
+      return null;
+    }
+
+    ArrayList<Attribute> duplicateList =
+         new ArrayList<Attribute>(attrList.size());
+    for (Attribute a : attrList)
+    {
+      if (a.hasOptions(options))
+      {
+        duplicateList.add(a.duplicate(omitValues));
+      }
+    }
+
+    if (duplicateList.isEmpty())
+    {
+      return null;
+    }
+    else
+    {
+      return duplicateList;
+    }
+  }
+
+
+
+  /**
+   * Retrieves a copy of the requested user attribute element(s) for
+   * the specified attribute type.  The list returned may include
+   * multiple elements if the same attribute exists in the entry
+   * multiple times with different sets of options.
+   *
+   * @param  attributeType  The attribute type to retrieve.
+   * @param  options        The set of attribute options to include in
+   *                        matching elements.
+   * @param  omitValues     <CODE>true</CODE> if the values are to be
+   *                        omitted.
+   *
+   * @return  A copy of the requested attribute element(s) for the
+   *          specified attribute type, or <CODE>null</CODE> if there
+   *          is no such user attribute with the specified set of
+   *          options.
+   */
+  public List<Attribute> duplicateUserAttribute(
+       AttributeType attributeType,
+       Set<String> options,
+       boolean omitValues)
+  {
+    assert debugEnter(CLASS_NAME, "duplicateUserAttribute",
+                      String.valueOf(attributeType),
+                      String.valueOf(options),
+                      String.valueOf(omitValues));
+
+    List<Attribute> currentList = userAttributes.get(attributeType);
+    return duplicateAttribute(currentList, options, omitValues);
+  }
+
+
+
+  /**
+   * Retrieves a copy of the requested operational attribute
+   * element(s) for the specified attribute type.  The list returned
+   * may include multiple elements if the same attribute exists in
+   * the entry multiple times with different sets of options.
+   *
+   * @param  attributeType  The attribute type to retrieve.
+   * @param  options        The set of attribute options to include in
+   *                        matching elements.
+   * @param  omitValues     <CODE>true</CODE> if the values are to be
+   *                        omitted.
+   *
+   * @return  A copy of the requested attribute element(s) for the
+   *          specified attribute type, or <CODE>null</CODE> if there
+   *          is no such user attribute with the specified set of
+   *          options.
+   */
+  public List<Attribute> duplicateOperationalAttribute(
+       AttributeType attributeType,
+       Set<String> options,
+       boolean omitValues)
+  {
+    assert debugEnter(CLASS_NAME, "duplicateOperationalAttribute",
+                      String.valueOf(attributeType),
+                      String.valueOf(options),
+                      String.valueOf(omitValues));
+
+    List<Attribute> currentList =
+         operationalAttributes.get(attributeType);
+    return duplicateAttribute(currentList, options, omitValues);
+  }
+
+
+  /**
    * Indicates whether this entry contains the specified operational
    * attribute.
    *
@@ -2660,12 +2776,12 @@
     HashMap<AttributeType,List<Attribute>> userAttrsCopy =
          new HashMap<AttributeType,List<Attribute>>(
               userAttributes.size());
-    deepCopy(userAttributes, userAttrsCopy);
+    deepCopy(userAttributes, userAttrsCopy, false);
 
     HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
          new HashMap<AttributeType,List<Attribute>>(
                   operationalAttributes.size());
-    deepCopy(operationalAttributes, operationalAttrsCopy);
+    deepCopy(operationalAttributes, operationalAttrsCopy, false);
 
     return new Entry(dnCopy, objectClassesCopy, userAttrsCopy,
                      operationalAttrsCopy);
@@ -2715,19 +2831,9 @@
       ArrayList<Attribute> ocList = new ArrayList<Attribute>(1);
       ocList.add(new Attribute(ocType));
       userAttrsCopy.put(ocType, ocList);
-
-
-      for (AttributeType t : userAttributes.keySet())
-      {
-        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-        attrList.add(new Attribute(t));
-        userAttrsCopy.put(t, attrList);
-      }
     }
-    else
-    {
-      deepCopy(userAttributes, userAttrsCopy);
-    }
+
+    deepCopy(userAttributes, userAttrsCopy, typesOnly);
 
     HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
          new HashMap<AttributeType,List<Attribute>>(0);
@@ -2747,9 +2853,12 @@
    *                 information.
    * @param  target  The target map into which to place the copied
    *                 information.
+   * @param  omitValues <CODE>true</CODE> if the values should be
+   *                    omitted.
    */
   private void deepCopy(Map<AttributeType,List<Attribute>> source,
-                        Map<AttributeType,List<Attribute>> target)
+                        Map<AttributeType,List<Attribute>> target,
+                        boolean omitValues)
   {
     for (AttributeType t : source.keySet())
     {
@@ -2759,7 +2868,7 @@
 
       for (Attribute a : sourceList)
       {
-        targetList.add(a.duplicate());
+        targetList.add(a.duplicate(omitValues));
       }
 
       target.put(t, targetList);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
index dab723b..abae8a5 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
@@ -36,6 +36,8 @@
 import org.opends.server.protocols.ldap.*;
 import org.opends.server.types.*;
 import org.opends.server.TestCaseUtils;
+import org.opends.server.controls.MatchedValuesFilter;
+import org.opends.server.controls.MatchedValuesControl;
 import org.opends.server.plugins.InvocationCounterPlugin;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -181,7 +183,8 @@
   }
 
   private SearchResultEntryProtocolOp searchExternalForSingleEntry(
-       SearchRequestProtocolOp searchRequest)
+       SearchRequestProtocolOp searchRequest,
+       ArrayList<LDAPControl> controls)
        throws IOException, LDAPException, ASN1Exception, InterruptedException
   {
     // Establish a connection to the server.
@@ -197,7 +200,7 @@
       InvocationCounterPlugin.resetAllCounters();
 
       LDAPMessage message;
-      message = new LDAPMessage(2, searchRequest);
+      message = new LDAPMessage(2, searchRequest, controls);
       w.writeElement(message.encode());
 
       message = LDAPMessage.decode(r.readElement().decodeAsSequence());
@@ -297,7 +300,8 @@
   }
 
   @Test
-  public void testSearchInternalAllUserAttributesTypesOnly() throws Exception
+  public void testSearchInternalUnspecifiedAttributesOmitValues()
+       throws Exception
   {
     InternalClientConnection conn =
          InternalClientConnection.getRootConnection();
@@ -467,7 +471,7 @@
               LDAPFilter.decode("(objectclass=inetorgperson)"),
               null);
     SearchResultEntryProtocolOp searchResultEntry =
-         searchExternalForSingleEntry(searchRequest);
+         searchExternalForSingleEntry(searchRequest, null);
     assertEquals(searchResultEntry.getAttributes().size(), ldapAttrCount);
   }
 
@@ -487,12 +491,36 @@
               LDAPFilter.decode("(objectclass=inetorgperson)"),
               attributes);
     SearchResultEntryProtocolOp searchResultEntry =
-         searchExternalForSingleEntry(searchRequest);
+         searchExternalForSingleEntry(searchRequest, null);
     assertEquals(searchResultEntry.getAttributes().size(), ldapAttrCount);
   }
 
   @Test
-  public void testSearchExternalAllUserAttributesTypesOnly() throws Exception
+  public void testSearchExternalUnspecifiedAttributesOmitValues()
+       throws Exception
+  {
+    SearchRequestProtocolOp searchRequest =
+         new SearchRequestProtocolOp(
+              new ASN1OctetString("o=test"),
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES,
+              Integer.MAX_VALUE,
+              Integer.MAX_VALUE,
+              true,
+              LDAPFilter.decode("(objectclass=inetorgperson)"),
+              null);
+    SearchResultEntryProtocolOp searchResultEntry =
+         searchExternalForSingleEntry(searchRequest, null);
+    // The attributes will include the objectclass type.
+    assertEquals(searchResultEntry.getAttributes().size(), ldapAttrCount);
+    for (LDAPAttribute a : searchResultEntry.getAttributes())
+    {
+      assertEquals(a.getValues().size(), 0);
+    }
+  }
+
+  @Test
+  public void testSearchExternalAllUserAttributesOmitValues() throws Exception
   {
     LinkedHashSet<String> attributes = new LinkedHashSet<String>();
     attributes.add("*");
@@ -507,10 +535,13 @@
               LDAPFilter.decode("(objectclass=inetorgperson)"),
               attributes);
     SearchResultEntryProtocolOp searchResultEntry =
-         searchExternalForSingleEntry(searchRequest);
+         searchExternalForSingleEntry(searchRequest, null);
     // The attributes will include the objectclass type.
-    assertEquals(searchResultEntry.getAttributes().size(),
-                 entry.getUserAttributes().size() + 1);
+    assertEquals(searchResultEntry.getAttributes().size(), ldapAttrCount);
+    for (LDAPAttribute a : searchResultEntry.getAttributes())
+    {
+      assertEquals(a.getValues().size(), 0);
+    }
   }
 
   @Test
@@ -530,7 +561,7 @@
               attributes);
 
     SearchResultEntryProtocolOp searchResultEntry =
-         searchExternalForSingleEntry(searchRequest);
+         searchExternalForSingleEntry(searchRequest, null);
 
     assertEquals(searchResultEntry.getAttributes().size(), 1);
     assertEquals(searchResultEntry.getAttributes().
@@ -538,6 +569,31 @@
   }
 
   @Test
+  public void testSearchExternalObjectClassAttributeOmitValues()
+       throws Exception
+  {
+    LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+    attributes.add("objectclass");
+    SearchRequestProtocolOp searchRequest =
+         new SearchRequestProtocolOp(
+              new ASN1OctetString("o=test"),
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES,
+              Integer.MAX_VALUE,
+              Integer.MAX_VALUE,
+              true,
+              LDAPFilter.decode("(objectclass=inetorgperson)"),
+              attributes);
+
+    SearchResultEntryProtocolOp searchResultEntry =
+         searchExternalForSingleEntry(searchRequest, null);
+
+    assertEquals(searchResultEntry.getAttributes().size(), 1);
+    assertEquals(searchResultEntry.getAttributes().
+         getFirst().getValues().size(), 0);
+  }
+
+  @Test
   public void testSearchExternalSelectedAttributes() throws Exception
   {
     LinkedHashSet<String> attributes = new LinkedHashSet<String>();
@@ -555,8 +611,119 @@
               attributes);
 
     SearchResultEntryProtocolOp searchResultEntry =
-         searchExternalForSingleEntry(searchRequest);
+         searchExternalForSingleEntry(searchRequest, null);
 
     assertEquals(searchResultEntry.getAttributes().size(), 2);
   }
+
+  @Test
+  public void testSearchExternalAttributeWithSubtypes() throws Exception
+  {
+    LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+    attributes.add("title");
+    SearchRequestProtocolOp searchRequest =
+         new SearchRequestProtocolOp(
+              new ASN1OctetString("o=test"),
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES,
+              Integer.MAX_VALUE,
+              Integer.MAX_VALUE,
+              false,
+              LDAPFilter.decode("(objectclass=inetorgperson)"),
+              attributes);
+
+    SearchResultEntryProtocolOp searchResultEntry =
+         searchExternalForSingleEntry(searchRequest, null);
+
+    assertEquals(searchResultEntry.getAttributes().size(), 4);
+  }
+
+  @Test
+  public void testSearchExternalAttributeWithSubtypesOmitValues()
+       throws Exception
+  {
+    LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+    attributes.add("title");
+    SearchRequestProtocolOp searchRequest =
+         new SearchRequestProtocolOp(
+              new ASN1OctetString("o=test"),
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES,
+              Integer.MAX_VALUE,
+              Integer.MAX_VALUE,
+              true,
+              LDAPFilter.decode("(objectclass=inetorgperson)"),
+              attributes);
+
+    SearchResultEntryProtocolOp searchResultEntry =
+         searchExternalForSingleEntry(searchRequest, null);
+
+    assertEquals(searchResultEntry.getAttributes().size(), 4);
+    for (LDAPAttribute a : searchResultEntry.getAttributes())
+    {
+      assertEquals(a.getValues().size(), 0);
+    }
+  }
+
+  @Test
+  public void testSearchExternalAttributeWithOptions() throws Exception
+  {
+    LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+    attributes.add("title;lang-ja;phonetic");
+    SearchRequestProtocolOp searchRequest =
+         new SearchRequestProtocolOp(
+              new ASN1OctetString("o=test"),
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES,
+              Integer.MAX_VALUE,
+              Integer.MAX_VALUE,
+              false,
+              LDAPFilter.decode("(objectclass=inetorgperson)"),
+              attributes);
+
+    SearchResultEntryProtocolOp searchResultEntry =
+         searchExternalForSingleEntry(searchRequest, null);
+
+    assertEquals(searchResultEntry.getAttributes().size(), 1);
+  }
+
+  @Test
+  public void testSearchExternalMatchedValues() throws Exception
+  {
+    // Add a matched values control.
+    LDAPFilter ldapFilter = LDAPFilter.decode("(title=*director*)");
+    MatchedValuesFilter matchedValuesFilter =
+         MatchedValuesFilter.createFromLDAPFilter(ldapFilter);
+    ArrayList<MatchedValuesFilter> filters =
+         new ArrayList<MatchedValuesFilter>();
+    filters.add(matchedValuesFilter);
+    MatchedValuesControl mvc = new MatchedValuesControl(true, filters);
+    ArrayList<LDAPControl> controls = new ArrayList<LDAPControl>();
+    controls.add(new LDAPControl(mvc));
+
+    SearchRequestProtocolOp searchRequest =
+         new SearchRequestProtocolOp(
+              new ASN1OctetString("o=test"),
+              SearchScope.WHOLE_SUBTREE,
+              DereferencePolicy.NEVER_DEREF_ALIASES,
+              Integer.MAX_VALUE,
+              Integer.MAX_VALUE,
+              false,
+              LDAPFilter.decode("(objectclass=inetorgperson)"),
+              null);
+
+    SearchResultEntryProtocolOp searchResultEntry =
+         searchExternalForSingleEntry(searchRequest, controls);
+
+    // Per RFC 3876, an attribute that has no values selected is returned
+    // with an empty set of values.  We should therefore expect all the
+    // attributes but only one value.
+    int valueCount = 0;
+    for (LDAPAttribute a : searchResultEntry.getAttributes())
+    {
+      valueCount += a.getValues().size();
+    }
+    assertEquals(valueCount, 1);
+  }
+
 }

--
Gitblit v1.10.0