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