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

matthew_swift
29.22.2009 9f4074cd344d25a5e1485fc59cbece7ff3c84648
Fix issue 3561: aliased attributes not returned properly

4 files modified
983 ■■■■■ changed files
opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java 269 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/SearchResultEntryProtocolOp.java 13 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/types/Entry.java 501 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SearchOperationTestCase.java 200 ●●●●● patch | view | raw | blame | history
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);
  }
}
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);
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);
          }
        }
      }
    }
  }
}
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);
  }
}