From 9f4074cd344d25a5e1485fc59cbece7ff3c84648 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Thu, 29 Jan 2009 16:22:43 +0000
Subject: [PATCH] Fix issue 3561: aliased attributes not returned properly

---
 opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/SearchResultEntryProtocolOp.java           |   13 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java |  200 +++++++++++
 opendj-sdk/opends/src/server/org/opends/server/types/Entry.java                                          |  501 ++++++++++++++++++++++++-----
 opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java                            |  269 ---------------
 4 files changed, 615 insertions(+), 368 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
index 456aa04..2285719 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -30,12 +30,10 @@
 import org.opends.messages.Message;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.plugin.PluginResult;
@@ -714,183 +712,10 @@
 
     // Make a copy of the entry and pare it down to only include the set
     // of requested attributes.
-    Entry entryToReturn;
-    boolean omitReal = isVirtualAttributesOnly();
-    boolean omitVirtual = isRealAttributesOnly();
-    if ((getAttributes() == null) || getAttributes().isEmpty())
-    {
-      entryToReturn =
-          entry.duplicateWithoutOperationalAttributes(typesOnly,
-              omitReal, omitVirtual);
-    }
-    else
-    {
-      entryToReturn = entry.duplicateWithoutAttributes();
+    Entry entryToReturn =
+        entry.filterEntry(getAttributes(), typesOnly,
+            isVirtualAttributesOnly(), isRealAttributesOnly());
 
-      for (String attrName : getAttributes())
-      {
-        if (attrName.equals("*"))
-        {
-          // This is a special placeholder indicating that all user attributes
-          // should be returned.
-          if (!omitReal)
-          {
-            if (typesOnly)
-            {
-              // First, add the placeholder for the objectclass
-              // attribute.
-              AttributeType ocType =
-                  DirectoryServer.getObjectClassAttributeType();
-              List<Attribute> ocList = new ArrayList<Attribute>(1);
-              ocList.add(Attributes.empty(ocType));
-              entryToReturn.putAttribute(ocType, ocList);
-            }
-            else
-            {
-              // First, add the objectclass attribute.
-              Attribute ocAttr = entry.getObjectClassAttribute();
-              if (ocAttr != null)
-              {
-                entryToReturn.replaceAttribute(ocAttr);
-              }
-            }
-          }
-
-          // Next iterate through all the user attributes and include them.
-          for (AttributeType t : entry.getUserAttributes().keySet())
-          {
-            List<Attribute> attrList =
-                duplicateUserAttribute(entry, t, null, typesOnly,
-                    omitReal, omitVirtual);
-            if (attrList != null)
-            {
-              entryToReturn.putAttribute(t, attrList);
-            }
-          }
-
-          continue;
-        }
-        else if (attrName.equals("+"))
-        {
-          // This is a special placeholder indicating that all operational
-          // attributes should be returned.
-          for (AttributeType t : entry.getOperationalAttributes().keySet())
-          {
-            List<Attribute> attrList =
-                duplicateOperationalAttribute(entry, t, null,
-                    typesOnly, omitReal, omitVirtual);
-            if (attrList != null)
-            {
-              entryToReturn.putAttribute(t, attrList);
-            }
-          }
-
-          continue;
-        }
-
-        String lowerName;
-        HashSet<String> options;
-        int semicolonPos = attrName.indexOf(';');
-        if (semicolonPos > 0)
-        {
-          lowerName = toLowerCase(attrName.substring(0, semicolonPos));
-          int nextPos = attrName.indexOf(';', semicolonPos+1);
-          options = new HashSet<String>();
-          while (nextPos > 0)
-          {
-            options.add(attrName.substring(semicolonPos+1, nextPos));
-
-            semicolonPos = nextPos;
-            nextPos = attrName.indexOf(';', semicolonPos+1);
-          }
-          options.add(attrName.substring(semicolonPos+1));
-        }
-        else
-        {
-          lowerName = toLowerCase(attrName);
-          options = null;
-        }
-
-        AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
-        if (attrType == null)
-        {
-          boolean added = false;
-          for (AttributeType t : entry.getUserAttributes().keySet())
-          {
-            if (t.hasNameOrOID(lowerName))
-            {
-              List<Attribute> attrList =
-                  duplicateUserAttribute(entry, t, options, typesOnly,
-                      omitReal, omitVirtual);
-              if (attrList != null)
-              {
-                entryToReturn.putAttribute(t, attrList);
-                added = true;
-                break;
-              }
-            }
-          }
-
-          if (added)
-          {
-            continue;
-          }
-
-          for (AttributeType t : entry.getOperationalAttributes().keySet())
-          {
-            if (t.hasNameOrOID(lowerName))
-            {
-              List<Attribute> attrList =
-                  duplicateOperationalAttribute(entry, t, options,
-                      typesOnly, omitReal, omitVirtual);
-              if (attrList != null)
-              {
-                entryToReturn.putAttribute(t, attrList);
-                break;
-              }
-            }
-          }
-        }
-        else
-        {
-          if (attrType.isObjectClassType()) {
-            if (!omitReal)
-            {
-              if (typesOnly)
-              {
-                AttributeType ocType =
-                    DirectoryServer.getObjectClassAttributeType();
-                List<Attribute> ocList = new ArrayList<Attribute>(1);
-                ocList.add(Attributes.empty(ocType));
-                entryToReturn.putAttribute(ocType, ocList);
-              }
-              else
-              {
-                List<Attribute> attrList = new ArrayList<Attribute>(1);
-                attrList.add(entry.getObjectClassAttribute());
-                entryToReturn.putAttribute(attrType, attrList);
-              }
-            }
-          }
-          else
-          {
-            List<Attribute> attrList =
-                duplicateOperationalAttribute(entry, attrType, options,
-                    typesOnly, omitReal, omitVirtual);
-            if (attrList == null)
-            {
-              attrList =
-                  duplicateUserAttribute(entry, attrType, options,
-                      typesOnly, omitReal, omitVirtual);
-            }
-            if (attrList != null)
-            {
-              entryToReturn.putAttribute(attrType, attrList);
-            }
-          }
-        }
-      }
-    }
 
     // If there is a matched values control, then further pare down the entry
     // based on the filters that it contains.
@@ -1626,92 +1451,4 @@
             ERR_SEARCH_BASE_DOESNT_EXIST.get(String.valueOf(getBaseDN()));
     appendErrorMessage(message);
   }
-
-
-  // Copies non-empty attributes.
-  private List<Attribute> duplicateAttribute(
-       List<Attribute> attrList,
-       Set<String> options,
-       boolean omitValues,
-       boolean omitReal,
-       boolean omitVirtual)
-  {
-    if (attrList == null)
-    {
-      return null;
-    }
-
-    ArrayList<Attribute> duplicateList =
-         new ArrayList<Attribute>(attrList.size());
-    for (Attribute a : attrList)
-    {
-      if (a.hasAllOptions(options))
-      {
-        if (omitReal && !a.isVirtual())
-        {
-          continue;
-        }
-        else if (omitVirtual && a.isVirtual())
-        {
-          continue;
-        }
-        else if (a.isEmpty())
-        {
-          continue;
-        }
-        else if (omitValues)
-        {
-          duplicateList.add(Attributes.empty(a));
-        }
-        else
-        {
-          duplicateList.add(a);
-        }
-      }
-    }
-
-    if (duplicateList.isEmpty())
-    {
-      return null;
-    }
-    else
-    {
-      return duplicateList;
-    }
-  }
-
-
-
-  // Copy a user attribute - may return null if the attribute was
-  // not found or if it was empty.
-  private List<Attribute> duplicateUserAttribute(
-       Entry entry,
-       AttributeType attributeType,
-       Set<String> options,
-       boolean omitValues,
-       boolean omitReal,
-       boolean omitVirtual)
-  {
-    List<Attribute> currentList = entry.getUserAttribute(attributeType);
-    return duplicateAttribute(currentList, options, omitValues,
-        omitReal, omitVirtual);
-  }
-
-
-
-  // Copy an operational attribute - may return null if the
-  // attribute was not found or if it was empty.
-  private List<Attribute> duplicateOperationalAttribute(
-       Entry entry,
-       AttributeType attributeType,
-       Set<String> options,
-       boolean omitValues,
-       boolean omitReal,
-       boolean omitVirtual)
-  {
-    List<Attribute> currentList =
-         entry.getOperationalAttribute(attributeType);
-    return duplicateAttribute(currentList, options, omitValues,
-        omitReal, omitVirtual);
-  }
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/SearchResultEntryProtocolOp.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/SearchResultEntryProtocolOp.java
index 9325e51..fdb9ee1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/SearchResultEntryProtocolOp.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/SearchResultEntryProtocolOp.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Copyright 2006-2009 Sun Microsystems, Inc.
  */
 package org.opends.server.protocols.ldap;
 
@@ -149,12 +149,6 @@
 
     attributes = new LinkedList<LDAPAttribute>();
 
-    Attribute ocAttr = searchEntry.getObjectClassAttribute();
-    if (ocAttr != null)
-    {
-      attributes.add(new LDAPAttribute(ocAttr));
-    }
-
     if (ldapVersion == 2)
     {
       // Merge attributes having the same type into a single
@@ -281,6 +275,7 @@
    *
    * @return  The BER type for this protocol op.
    */
+  @Override
   public byte getType()
   {
     return OP_TYPE_SEARCH_RESULT_ENTRY;
@@ -293,6 +288,7 @@
    *
    * @return  The name for this protocol op type.
    */
+  @Override
   public String getProtocolOpName()
   {
     return "Search Result Entry";
@@ -306,6 +302,7 @@
    *
    * @return  The ASN.1 element containing the encoded protocol op.
    */
+  @Override
   public ASN1Element encode()
   {
     ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
@@ -422,6 +419,7 @@
    *
    * @param  buffer  The buffer to which the string should be appended.
    */
+  @Override
   public void toString(StringBuilder buffer)
   {
     buffer.append("SearchResultEntry(dn=");
@@ -453,6 +451,7 @@
    * @param  indent  The number of spaces from the margin that the lines should
    *                 be indented.
    */
+  @Override
   public void toString(StringBuilder buffer, int indent)
   {
     StringBuilder indentBuf = new StringBuilder(indent);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java b/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
index 46a274f..c5ecce2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
@@ -34,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -2884,69 +2885,6 @@
 
 
   /**
-   * Creates a duplicate of this entry without any operational
-   * attributes that may be altered without impacting the
-   * information in this entry.
-   * <p>
-   * TODO: this method is very specific to search result
-   * processing but we are forced to have it here due to tight
-   * coupling and performance reasons.
-   *
-   * @param typesOnly
-   *          Indicates whether to include attribute types only
-   *          without values.
-   * @param omitReal
-   *          Indicates whether to exclude real attributes.
-   * @param omitVirtual
-   *          Indicates whether to exclude virtual attributes.
-   * @return A duplicate of this entry that may be altered without
-   *         impacting the information in this entry and that does not
-   *         contain any operational attributes.
-   */
-  public Entry duplicateWithoutOperationalAttributes(
-      boolean typesOnly, boolean omitReal, boolean omitVirtual)
-  {
-    HashMap<ObjectClass,String> objectClassesCopy;
-    if (typesOnly || omitReal)
-    {
-      objectClassesCopy = new HashMap<ObjectClass,String>(0);
-    }
-    else
-    {
-      objectClassesCopy =
-           new HashMap<ObjectClass,String>(objectClasses);
-    }
-
-    HashMap<AttributeType,List<Attribute>> userAttrsCopy =
-         new HashMap<AttributeType,List<Attribute>>(
-              userAttributes.size());
-
-    if (typesOnly && !omitReal)
-    {
-      // Make sure to include the objectClass attribute here because
-      // it won't make it in otherwise.
-      AttributeType ocType =
-           DirectoryServer.getObjectClassAttributeType();
-      ArrayList<Attribute> ocList = new ArrayList<Attribute>(1);
-      ocList.add(Attributes.empty(ocType));
-      userAttrsCopy.put(ocType, ocList);
-    }
-
-    deepCopy(userAttributes, userAttrsCopy, typesOnly, true,
-        omitReal, omitVirtual);
-
-    HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
-         new HashMap<AttributeType,List<Attribute>>(0);
-
-    Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
-                        operationalAttrsCopy);
-
-    return e;
-  }
-
-
-
-  /**
    * Performs a deep copy from the source map to the target map.
    * In this case, the attributes in the list will be duplicates
    * rather than re-using the same reference.
@@ -3014,34 +2952,6 @@
 
 
   /**
-   * Creates a duplicate of this entry without any attribute or
-   * objectclass information (i.e., it will just contain the DN and
-   * placeholders for adding attributes) and objectclasses.
-   *
-   * @return  A duplicate of this entry that may be altered without
-   *          impacting the information in this entry and that does
-   *          not contain attribute or objectclass information.
-   */
-  public Entry duplicateWithoutAttributes()
-  {
-    HashMap<ObjectClass,String> objectClassesCopy =
-         new HashMap<ObjectClass,String>(objectClasses.size());
-
-    HashMap<AttributeType,List<Attribute>> userAttrsCopy =
-         new HashMap<AttributeType,List<Attribute>>(
-              userAttributes.size());
-
-    HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
-         new HashMap<AttributeType,List<Attribute>>(
-                  operationalAttributes.size());
-
-    return new Entry(dn, objectClassesCopy, userAttrsCopy,
-                     operationalAttrsCopy);
-  }
-
-
-
-  /**
    * Indicates whether this entry meets the criteria to consider it a
    * referral (e.g., it contains the "referral" objectclass and a
    * "ref" attribute).
@@ -5782,7 +5692,8 @@
         }
       }
 
-      return;
+      // Fall through - search results have an object attribute
+      // as well.
     }
 
     List<Attribute> attributes;
@@ -5875,5 +5786,411 @@
 
     attributes.add(attribute);
   }
+
+
+
+  /**
+   * Returns an entry containing only those attributes of this entry
+   * which match the provided criteria.
+   *
+   * @param attrNameList
+   *          The list of attributes to include, may include wild
+   *          cards.
+   * @param omitValues
+   *          Indicates whether to omit attribute values when
+   *          processing.
+   * @param omitReal
+   *          Indicates whether to exclude real attributes.
+   * @param omitVirtual
+   *          Indicates whether to exclude virtual attributes.
+   * @return An entry containing only those attributes of this entry
+   *         which match the provided criteria.
+   */
+  public Entry filterEntry(Set<String> attrNameList,
+      boolean omitValues, boolean omitReal, boolean omitVirtual)
+  {
+    HashMap<ObjectClass, String> objectClassesCopy;
+    HashMap<AttributeType, List<Attribute>> userAttrsCopy;
+    HashMap<AttributeType, List<Attribute>> operationalAttrsCopy;
+
+    if (attrNameList == null || attrNameList.isEmpty())
+    {
+      // Common case: return filtered user attributes.
+      userAttrsCopy =
+          new HashMap<AttributeType, List<Attribute>>(userAttributes
+              .size());
+      operationalAttrsCopy =
+          new HashMap<AttributeType, List<Attribute>>(0);
+
+      if (omitReal)
+      {
+        objectClassesCopy = new HashMap<ObjectClass, String>(0);
+      }
+      else if (omitValues)
+      {
+        objectClassesCopy = new HashMap<ObjectClass, String>(0);
+
+        // Add empty object class attribute.
+        AttributeType ocType =
+            DirectoryServer.getObjectClassAttributeType();
+        ArrayList<Attribute> ocList = new ArrayList<Attribute>(1);
+        ocList.add(Attributes.empty(ocType));
+        userAttrsCopy.put(ocType, ocList);
+      }
+      else
+      {
+        objectClassesCopy =
+            new HashMap<ObjectClass, String>(objectClasses);
+
+        // First, add the objectclass attribute.
+        Attribute ocAttr = getObjectClassAttribute();
+        if (ocAttr != null)
+        {
+          AttributeType ocType =
+              DirectoryServer.getObjectClassAttributeType();
+          ArrayList<Attribute> ocList = new ArrayList<Attribute>(1);
+          ocList.add(ocAttr);
+          userAttrsCopy.put(ocType, ocList);
+        }
+      }
+
+      // Copy all user attributes.
+      deepCopy(userAttributes, userAttrsCopy, omitValues, true,
+          omitReal, omitVirtual);
+    }
+    else
+    {
+      // Incrementally build table of attributes.
+      if (omitReal || omitValues)
+      {
+        objectClassesCopy = new HashMap<ObjectClass, String>(0);
+      }
+      else
+      {
+        objectClassesCopy =
+            new HashMap<ObjectClass, String>(objectClasses.size());
+      }
+
+      userAttrsCopy =
+          new HashMap<AttributeType, List<Attribute>>(userAttributes
+              .size());
+      operationalAttrsCopy =
+          new HashMap<AttributeType, List<Attribute>>(
+              operationalAttributes.size());
+
+      for (String attrName : attrNameList)
+      {
+        if (attrName.equals("*"))
+        {
+          // This is a special placeholder indicating that all user
+          // attributes should be returned.
+          if (!omitReal)
+          {
+            if (omitValues)
+            {
+              // Add empty object class attribute.
+              AttributeType ocType =
+                  DirectoryServer.getObjectClassAttributeType();
+              ArrayList<Attribute> ocList =
+                new ArrayList<Attribute>(1);
+              ocList.add(Attributes.empty(ocType));
+              userAttrsCopy.put(ocType, ocList);
+            }
+            else
+            {
+              // Add the objectclass attribute.
+              objectClassesCopy.putAll(objectClasses);
+              Attribute ocAttr = getObjectClassAttribute();
+              if (ocAttr != null)
+              {
+                AttributeType ocType =
+                    DirectoryServer.getObjectClassAttributeType();
+                ArrayList<Attribute> ocList =
+                  new ArrayList<Attribute>(1);
+                ocList.add(ocAttr);
+                userAttrsCopy.put(ocType, ocList);
+              }
+            }
+          }
+
+          // Copy all user attributes.
+          deepCopy(userAttributes, userAttrsCopy, omitValues, true,
+              omitReal, omitVirtual);
+
+          continue;
+        }
+        else if (attrName.equals("+"))
+        {
+          // This is a special placeholder indicating that all
+          // operational attributes should be returned.
+          deepCopy(operationalAttributes, operationalAttrsCopy,
+              omitValues, true, omitReal, omitVirtual);
+
+          continue;
+        }
+
+        String lowerName;
+        HashSet<String> options;
+        int semicolonPos = attrName.indexOf(';');
+        if (semicolonPos > 0)
+        {
+          String tmpName = attrName.substring(0, semicolonPos);
+          lowerName = toLowerCase(tmpName);
+          int nextPos = attrName.indexOf(';', semicolonPos+1);
+          options = new HashSet<String>();
+          while (nextPos > 0)
+          {
+            options.add(attrName.substring(semicolonPos+1, nextPos));
+
+            semicolonPos = nextPos;
+            nextPos = attrName.indexOf(';', semicolonPos+1);
+          }
+          options.add(attrName.substring(semicolonPos+1));
+          attrName = tmpName;
+        }
+        else
+        {
+          lowerName = toLowerCase(attrName);
+          options = null;
+        }
+
+        AttributeType attrType =
+          DirectoryServer.getAttributeType(lowerName);
+        if (attrType == null)
+        {
+          // Unrecognized attribute type - do best effort search.
+          for (Map.Entry<AttributeType, List<Attribute>> e :
+            userAttributes.entrySet())
+          {
+            AttributeType t = e.getKey();
+            if (t.hasNameOrOID(lowerName))
+            {
+              mergeAttributeLists(e.getValue(), userAttrsCopy, t,
+                  attrName, options, omitValues, omitReal,
+                  omitVirtual);
+              continue;
+            }
+          }
+
+          for (Map.Entry<AttributeType, List<Attribute>> e :
+            operationalAttributes.entrySet())
+          {
+            AttributeType t = e.getKey();
+            if (t.hasNameOrOID(lowerName))
+            {
+              mergeAttributeLists(e.getValue(), userAttrsCopy, t,
+                  attrName, options, omitValues, omitReal,
+                  omitVirtual);
+              continue;
+            }
+          }
+        }
+        else
+        {
+          // Recognized attribute type.
+          if (attrType.isObjectClassType()) {
+            if (!omitReal)
+            {
+              if (omitValues)
+              {
+                AttributeType ocType =
+                    DirectoryServer.getObjectClassAttributeType();
+                List<Attribute> ocList = new ArrayList<Attribute>(1);
+                ocList.add(Attributes.empty(ocType, attrName));
+                userAttrsCopy.put(ocType, ocList);
+              }
+              else
+              {
+                Attribute ocAttr = getObjectClassAttribute();
+                if (ocAttr != null)
+                {
+                  AttributeType ocType =
+                      DirectoryServer.getObjectClassAttributeType();
+
+                  if (!attrName.equals(ocAttr.getName()))
+                  {
+                    // User requested non-default object class type
+                    // name.
+                    AttributeBuilder builder =
+                        new AttributeBuilder(ocAttr);
+                    builder.setAttributeType(ocType, attrName);
+                    ocAttr = builder.toAttribute();
+                  }
+
+                  List<Attribute> ocList =
+                    new ArrayList<Attribute>(1);
+                  ocList.add(ocAttr);
+                  userAttrsCopy.put(ocType, ocList);
+                }
+              }
+            }
+          }
+          else
+          {
+            List<Attribute> attrList = getUserAttribute(attrType);
+            if (attrList != null)
+            {
+              mergeAttributeLists(attrList, userAttrsCopy, attrType,
+                  attrName, options, omitValues, omitReal,
+                  omitVirtual);
+            }
+            else
+            {
+              attrList = getOperationalAttribute(attrType);
+              if (attrList != null)
+              {
+                mergeAttributeLists(attrList, operationalAttrsCopy,
+                    attrType, attrName, options, omitValues, omitReal,
+                    omitVirtual);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return new Entry(dn, objectClassesCopy, userAttrsCopy,
+                     operationalAttrsCopy);
+  }
+
+
+
+  /**
+   * Copies the provided list of attributes into the destination
+   * attribute map according to the provided criteria.
+   *
+   * @param sourceList
+   *          The list containing the attributes to be copied.
+   * @param destMap
+   *          The map where the attributes should be copied to.
+   * @param attrType
+   *          The attribute type.
+   * @param attrName
+   *          The user-provided attribute name.
+   * @param options
+   *          The user-provided attribute options.
+   * @param omitValues
+   *          Indicates whether to exclude attribute values.
+   * @param omitReal
+   *          Indicates whether to exclude real attributes.
+   * @param omitVirtual
+   *          Indicates whether to exclude virtual attributes.
+   */
+  private void mergeAttributeLists(List<Attribute> sourceList,
+      HashMap<AttributeType, List<Attribute>> destMap,
+      AttributeType attrType, String attrName,
+      HashSet<String> options, boolean omitValues, boolean omitReal,
+      boolean omitVirtual)
+  {
+    if (sourceList == null)
+    {
+      return;
+    }
+
+    for (Attribute attribute : sourceList)
+    {
+      if (attribute.isEmpty())
+      {
+        continue;
+      }
+      else if (omitReal && !attribute.isVirtual())
+      {
+        continue;
+      }
+      else if (omitVirtual && attribute.isVirtual())
+      {
+        continue;
+      }
+      else if (!attribute.hasAllOptions(options))
+      {
+        continue;
+      }
+      else
+      {
+        // If a non-default attribute name was provided or if the
+        // attribute has options then we will need to rebuild the
+        // attribute so that it contains the user-requested names and
+        // options.
+        AttributeType subAttrType = attribute.getAttributeType();
+
+        if ((attrName != null
+            && !attrName.equals(attribute.getName()))
+            || (options != null && !options.isEmpty()))
+        {
+          AttributeBuilder builder = new AttributeBuilder();
+
+          // We want to use the user-provided name only if this
+          // attribute has the same type as the requested type. This
+          // might not be the case for sub-types e.g. requesting
+          // "name" and getting back "cn" - we don't want to rename
+          // "name" to "cn".
+          if (attrName == null || !subAttrType.equals(attrType))
+          {
+            builder.setAttributeType(attribute.getAttributeType(),
+                attribute.getName());
+          }
+          else
+          {
+            builder.setAttributeType(attribute.getAttributeType(),
+                attrName);
+          }
+
+          if (options != null)
+          {
+            builder.setOptions(options);
+          }
+
+          // Now add in remaining options from original attribute
+          // (this will not overwrite options already present).
+          builder.setOptions(attribute.getOptions());
+
+          if (!omitValues)
+          {
+            builder.addAll(attribute);
+          }
+
+          attribute = builder.toAttribute();
+        }
+        else if (omitValues)
+        {
+          attribute = Attributes.empty(attribute);
+        }
+
+        // Now put the attribute into the destination map.
+        // Be careful of duplicates.
+        List<Attribute> attrList = destMap.get(subAttrType);
+
+        if (attrList == null)
+        {
+          // Assume that they'll all go in the one list. This isn't
+          // always the case, for example if the list contains
+          // sub-types.
+          attrList = new ArrayList<Attribute>(sourceList.size());
+          attrList.add(attribute);
+          destMap.put(subAttrType, attrList);
+        }
+        else
+        {
+          // The attribute may have already been put in the list
+          // - lets replace it assuming that the previous version
+          // was added using a wildcard and that this version has
+          // a user provided name and/or options.
+          boolean found = false;
+          for (int i = 0; i < attrList.size(); i++)
+          {
+            if (attrList.get(i).optionsEqual(attribute.getOptions()))
+            {
+              attrList.set(i, attribute);
+              found = true;
+            }
+          }
+          if (!found)
+          {
+            attrList.add(attribute);
+          }
+        }
+      }
+    }
+  }
 }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
index adb8175..4cb0cd9 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java
@@ -45,13 +45,16 @@
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
+
 import static org.testng.Assert.*;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 import java.net.Socket;
 import java.io.IOException;
 
@@ -390,8 +393,11 @@
     Entry resultEntry = searchInternalForSingleEntry(searchOperation);
 
     assertEquals(resultEntry.getObjectClasses(), testEntry.getObjectClasses());
+
+    // Search results contain objectClass as an attribute.
     assertEquals(resultEntry.getUserAttributes().size(),
-                 testEntry.getUserAttributes().size());
+                 testEntry.getUserAttributes().size() + 1);
+
     assertEquals(resultEntry.getOperationalAttributes().size(), 0);
   }
 
@@ -485,8 +491,10 @@
 
     assertEquals(resultEntry.getObjectClasses(), testEntry.getObjectClasses());
     assertTrue(resultEntry.getOperationalAttributes().size() > 0);
+
+    // Search results contain objectClass as an attribute.
     assertEquals(resultEntry.getUserAttributes().size(),
-                 testEntry.getUserAttributes().size());
+                 testEntry.getUserAttributes().size() + 1);
   }
 
   @Test
@@ -517,8 +525,11 @@
     Entry resultEntry = searchInternalForSingleEntry(searchOperation);
 
     assertEquals(resultEntry.getObjectClasses(), testEntry.getObjectClasses());
+
+    // Search results contain objectClass as an attribute.
     assertEquals(resultEntry.getUserAttributes().size(),
-                 testEntry.getUserAttributes().size());
+                 testEntry.getUserAttributes().size() + 1);
+
     assertEquals(resultEntry.getOperationalAttributes().size(), 1);
   }
 
@@ -1143,4 +1154,187 @@
 
     assertTrue(messages.isEmpty(), "Entry invalid: " + messages);
   }
+
+
+
+  /**
+   * Returns test data for testSearchInternalUserAttributeNames.
+   *
+   * @return The test data.
+   */
+  @DataProvider(name = "testSearchInternalUserAttributeNames")
+  public Object[][] createTestSearchInternalUserAttributeNamesData()
+  {
+    // First array is the requested attributes.
+    // Second array is the expected attribute names in the entry.
+    return new Object[][] {
+        {
+            Arrays.<String>asList(),
+            Arrays.asList("objectClass", "cn", "cn;lang-fr") },
+        {
+            Arrays.asList("*", "+"),
+            Arrays.asList("objectClass", "cn", "cn;lang-fr", "entryDN",
+                "createTimestamp") },
+        {
+            Arrays.asList("objectClass", "cn", "cn;lang-fr", "entryDN",
+                "createTimestamp"),
+            Arrays.asList("objectClass", "cn", "cn;lang-fr", "entryDN",
+                "createTimestamp") },
+        {
+            Arrays.asList("OBJECTCLASS", "commonName", "commonName;LANG-FR", "entrydn",
+                "CREATETIMESTAMP"),
+            Arrays.asList("OBJECTCLASS", "commonName",
+                "commonName;LANG-FR", "entrydn", "CREATETIMESTAMP") },
+        {
+            Arrays.asList("*", "+", "OBJECTCLASS", "commonName",
+                "commonName;LANG-FR", "entrydn", "CREATETIMESTAMP"),
+            Arrays.asList("OBJECTCLASS", "commonName",
+                "commonName;LANG-FR", "entrydn", "CREATETIMESTAMP") },
+        { Arrays.asList("name"),
+            Arrays.asList("givenName", "sn", "cn", "cn;lang-fr") },
+        { Arrays.asList("name;lang-fr"), Arrays.asList("cn;lang-fr") },
+        { Arrays.asList("name;LANG-FR"), Arrays.asList("cn;LANG-FR") }, };
+  }
+
+
+
+  /**
+   * Tests that attributes are returned from internal searches using the
+   * attribute name requested by the user.
+   *
+   * @param requestedAttributes
+   *          The list of requested attributes names.
+   * @param expectedAttributes
+   *          The list of expected attribute names.
+   * @throws Exception
+   *           If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testSearchInternalUserAttributeNames")
+  public void testSearchInternalUserAttributeNames(
+      List<String> requestedAttributes, List<String> expectedAttributes)
+      throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String userDNString = "uid=test.user,o=test";
+    DN userDN = DN.decode(userDNString);
+
+    TestCaseUtils.addEntry("dn: " + userDNString,
+        "objectClass: top",
+        "objectClass: person",
+        "objectClass: organizationalPerson",
+        "objectClass: inetOrgPerson",
+        "uid: test.user",
+        "givenName: Test",
+        "sn: User",
+        "cn: Test User",
+        "cn;lang-fr: Test Usager",
+        "userPassword: password");
+
+    Entry userEntry = DirectoryServer.getEntry(userDN);
+    assertNotNull(userEntry);
+
+    LinkedHashSet<String> attributes =
+      new LinkedHashSet<String>(requestedAttributes);
+
+    InternalClientConnection conn =
+      InternalClientConnection.getRootConnection();
+
+    InternalSearchOperation search =
+      conn.processSearch(userDNString, SearchScope.BASE_OBJECT,
+          DereferencePolicy.NEVER_DEREF_ALIASES, 0, // Size limit
+          0, // Time limit
+          false, // Types only
+          "(objectClass=*)", attributes);
+
+    assertEquals(search.getResultCode(), ResultCode.SUCCESS);
+
+    LinkedList<SearchResultEntry> entries = search.getSearchEntries();
+    assertEquals(entries.size(), 1);
+
+    Entry entry = entries.getFirst();
+    assertEquals(entry.getDN(), userDN);
+
+    // Check all expected attributes are present and have
+    // the user requested name.
+    List<Attribute> attrList = entry.getAttributes();
+    Set<String> actualNames = new HashSet<String>();
+    for (Attribute attribute : attrList)
+    {
+      actualNames.add(attribute.getNameWithOptions());
+    }
+
+    assertTrue(actualNames.containsAll(expectedAttributes),
+        "Expected: " + expectedAttributes + " got " + actualNames);
+  }
+
+
+
+  /**
+   * Tests that attributes are returned from external searches using the
+   * attribute name requested by the user.
+   *
+   * @param requestedAttributes
+   *          The list of requested attributes names.
+   * @param expectedAttributes
+   *          The list of expected attribute names.
+   * @throws Exception
+   *           If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "testSearchInternalUserAttributeNames")
+  public void testSearchExternalUserAttributeNames(
+      List<String> requestedAttributes, List<String> expectedAttributes)
+      throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String userDNString = "uid=test.user,o=test";
+    DN userDN = DN.decode(userDNString);
+
+    TestCaseUtils.addEntry("dn: " + userDNString,
+        "objectClass: top",
+        "objectClass: person",
+        "objectClass: organizationalPerson",
+        "objectClass: inetOrgPerson",
+        "uid: test.user",
+        "givenName: Test",
+        "sn: User",
+        "cn: Test User",
+        "cn;lang-fr: Test Usager",
+        "userPassword: password");
+
+    Entry userEntry = DirectoryServer.getEntry(userDN);
+    assertNotNull(userEntry);
+
+    LinkedHashSet<String> attributes =
+      new LinkedHashSet<String>(requestedAttributes);
+
+    SearchRequestProtocolOp searchRequest =
+      new SearchRequestProtocolOp(
+          new ASN1OctetString(userDNString),
+          SearchScope.BASE_OBJECT,
+          DereferencePolicy.NEVER_DEREF_ALIASES,
+          Integer.MAX_VALUE,
+          Integer.MAX_VALUE,
+          false,
+          LDAPFilter.decode("(objectclass=*)"),
+          attributes);
+
+    SearchResultEntryProtocolOp entry =
+      searchExternalForSingleEntry(searchRequest, null);
+
+    assertEquals(entry.getDN(), userDN);
+
+    // Check all expected attributes are present and have
+    // the user requested name.
+    LinkedList<LDAPAttribute> attrList = entry.getAttributes();
+    Set<String> actualNames = new HashSet<String>();
+    for (LDAPAttribute attribute : attrList)
+    {
+      actualNames.add(attribute.getAttributeType());
+    }
+
+    assertTrue(actualNames.containsAll(expectedAttributes),
+        "Expected: " + expectedAttributes + " got " + actualNames);
+  }
 }

--
Gitblit v1.10.0