diff --git a/core/build.gradle b/core/build.gradle
index 7eb89458..1cb69792 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -13,6 +13,7 @@ dependencies {
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-tx:$springVersion",
"org.springframework.data:spring-data-commons:$springDataVersion"
+ "org.slf4j:slf4j-api:$slf4jVersion"
provided "commons-pool:commons-pool:$commonsPoolVersion",
"com.sun:ldapbp:1.0",
@@ -24,6 +25,7 @@ dependencies {
testCompile "junit:junit:$junitVersion",
"commons-lang:commons-lang:$commonsLangVersion",
"gsbase:gsbase:$gsbaseVersion",
- "org.mockito:mockito-core:$mockitoVersion"
+ "org.mockito:mockito-core:$mockitoVersion",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
}
diff --git a/core/src/main/java/org/springframework/LdapDataEntry.java b/core/src/main/java/org/springframework/LdapDataEntry.java
index bb1e882b..b52b36c4 100644
--- a/core/src/main/java/org/springframework/LdapDataEntry.java
+++ b/core/src/main/java/org/springframework/LdapDataEntry.java
@@ -47,9 +47,14 @@ public interface LdapDataEntry {
/**
* Set the with the name name to the value.
+ * If the value is a {@link Name} instance, equality for Distinguished
+ * Names will be used for calculating attribute modifications.
*
* @param name name of the attribute.
* @param value value to set the attribute to.
+ * @throws IllegalArgumentException if the value is a {@link Name} instance
+ * and one or several of the currently present attribute values is not
+ * {@link Name} instances or Strings representing valid Distinguished Names.
*/
public void setAttributeValue(String name, Object value);
@@ -62,8 +67,14 @@ public interface LdapDataEntry {
* objects or if one or more object has changed. Reordering the objects will
* not cause an update.
*
+ * If the values are {@link Name} instances, equality for Distinguished
+ * Names will be used for calculating attribute modifications.
+ *
* @param name The id of the attribute.
* @param values Attribute values.
+ * @throws IllegalArgumentException if value is a {@link Name} instance
+ * and one or several of the currently present attribute values is not
+ * {@link Name} instances or Strings representing valid Distinguished Names.
*/
void setAttributeValues(String name, Object[] values);
@@ -78,10 +89,15 @@ public interface LdapDataEntry {
* Reordering the objects will only cause an update if orderMatters is set
* to true.
*
+ * If the values are {@link Name} instances, equality for Distinguished
+ * Names will be used for calculating attribute modifications.
* @param name The id of the attribute.
* @param values Attribute values.
* @param orderMatters If true, it will be changed even if data
* was just reordered.
+ * @throws IllegalArgumentException if value is a {@link Name} instance
+ * and one or several of the currently present attribute values is not
+ * {@link Name} instances or Strings representing valid Distinguished Names.
*/
void setAttributeValues(String name, Object[] values, boolean orderMatters);
@@ -91,9 +107,15 @@ public interface LdapDataEntry {
* will be no duplicates of an added value - it the value exists it will not
* be added again.
*
+ * If the value is a {@link Name} instance, equality for Distinguished
+ * Names will be used for calculating attribute modifications.
+ *
* @param name the name of the Attribute to which the specified value should
* be added.
* @param value the Attribute value to add.
+ * @throws IllegalArgumentException if value is a {@link Name} instance
+ * and one or several of the currently present attribute values is not
+ * {@link Name} instances or Strings representing valid Distinguished Names.
*/
void addAttributeValue(String name, Object value);
@@ -104,6 +126,9 @@ public interface LdapDataEntry {
* this method makes sure that the there will be no duplicates of an added
* value - it the value exists it will not be added again.
*
+ * If the value is a {@link Name} instance, equality for Distinguished
+ * Names will be used for calculating attribute modifications.
+ *
* @param name the name of the Attribute to which the specified value should
* be added.
* @param value the Attribute value to add.
@@ -111,6 +136,9 @@ public interface LdapDataEntry {
* regardless of whether there is an identical value already, allowing for
* duplicate attribute values; false will not add the value if
* it already exists.
+ * @throws IllegalArgumentException if value is a {@link Name} instance
+ * and one or several of the currently present attribute values is not
+ * {@link Name} instances or Strings representing valid Distinguished Names.
*/
void addAttributeValue(String name, Object value,
boolean addIfDuplicateExists);
@@ -119,9 +147,15 @@ public interface LdapDataEntry {
* Remove a value from the Attribute with the specified name. If the
* Attribute doesn't exist, do nothing.
*
+ * If the value is a {@link Name} instance, equality for Distinguished
+ * Names will be used for calculating attribute modifications.
+ *
* @param name the name of the Attribute from which the specified value
* should be removed.
* @param value the value to remove.
+ * @throws IllegalArgumentException if value is a {@link Name} instance
+ * and one or several of the currently present attribute values is not
+ * {@link Name} instances or Strings representing valid Distinguished Names.
*/
void removeAttributeValue(String name, Object value);
diff --git a/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java b/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java
index 228f6c83..2ba6c4fd 100644
--- a/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java
+++ b/core/src/main/java/org/springframework/ldap/core/DirContextAdapter.java
@@ -33,8 +33,6 @@ import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
@@ -60,10 +58,27 @@ import java.util.TreeSet;
* this class keeps track of the changes made to its attributes, making them
* available as an array of ModificationItem objects, suitable as
* input to {@link LdapTemplate#modifyAttributes(DirContextOperations)}.
- *
- * Note that this is not a complete implementation of DirContext. Several
- * methods are not relevant for the intended usage of this class, so they
- * throw UnsupportOperationException.
+ *
+ *
+ * This class is aware of the specifics of {@link Name} instances with regards
+ * to equality when working with attribute values. This comes in very handy
+ * when working with e.g. security groups and modifications of them. If
+ * {@link Name} instances are supplied to one of the Attribute manipulation
+ * methods (e.g. {@link #addAttributeValue(String, Object)},
+ * {@link #removeAttributeValue(String, Object)}, {@link #setAttributeValue(String, Object)},
+ * or {@link #setAttributeValues(String, Object[])}), the produced modifications
+ * will be calculated using {@link Name} equality. This means that if an the member
+ * has a value of "cn=John Doe,ou=People", and we call
+ * addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe,OU=People"),
+ * this will not be considered a modification since the two DN
+ * strings represent the same distinguished name (case and spacing between attributes is
+ * disregarded).
+ *
+ *
+ * Note that this is not a complete implementation of DirContext. Several
+ * methods are not relevant for the intended usage of this class, so they
+ * throw UnsupportOperationException.
+ *
*
* @see #setAttributeValue(String, Object)
* @see #setAttributeValues(String, Object[])
@@ -90,7 +105,7 @@ public class DirContextAdapter implements DirContextOperations {
private static Logger log = LoggerFactory.getLogger(DirContextAdapter.class);
- private final Attributes originalAttrs;
+ private final NameAwareAttributes originalAttrs;
private LdapName dn;
@@ -98,7 +113,7 @@ public class DirContextAdapter implements DirContextOperations {
private boolean updateMode = false;
- private Attributes updatedAttrs;
+ private NameAwareAttributes updatedAttrs;
private String referralUrl;
@@ -160,10 +175,10 @@ public class DirContextAdapter implements DirContextOperations {
public DirContextAdapter(Attributes attrs, Name dn, Name base,
String referralUrl) {
if (attrs != null) {
- this.originalAttrs = attrs;
+ this.originalAttrs = new NameAwareAttributes(attrs);
}
else {
- this.originalAttrs = new BasicAttributes(true);
+ this.originalAttrs = new NameAwareAttributes();
}
if (dn != null) {
@@ -193,9 +208,9 @@ public class DirContextAdapter implements DirContextOperations {
* @param master The adapter to be copied.
*/
protected DirContextAdapter(DirContextAdapter master) {
- this.originalAttrs = (Attributes) master.originalAttrs.clone();
+ this.originalAttrs = (NameAwareAttributes) master.originalAttrs.clone();
this.dn = master.dn;
- this.updatedAttrs = (Attributes) master.updatedAttrs.clone();
+ this.updatedAttrs = (NameAwareAttributes) master.updatedAttrs.clone();
this.updateMode = master.updateMode;
}
@@ -209,21 +224,14 @@ public class DirContextAdapter implements DirContextOperations {
public void setUpdateMode(boolean mode) {
this.updateMode = mode;
if (updateMode) {
- updatedAttrs = new BasicAttributes(true);
+ updatedAttrs = new NameAwareAttributes();
}
}
- /*
- * @see org.springframework.ldap.support.DirContextOperations#isUpdateMode()
- */
public boolean isUpdateMode() {
return updateMode;
}
- /*
- * @seeorg.springframework.ldap.support.DirContextOperations#
- * getNamesOfModifiedAttributes()
- */
public String[] getNamesOfModifiedAttributes() {
List tmpList = new ArrayList();
@@ -264,10 +272,6 @@ public class DirContextAdapter implements DirContextOperations {
}
}
- /*
- * @seeorg.springframework.ldap.support.AttributeModificationsAware#
- * getModificationItems()
- */
public ModificationItem[] getModificationItems() {
if (!updateMode) {
return new ModificationItem[0];
@@ -280,7 +284,7 @@ public class DirContextAdapter implements DirContextOperations {
// find attributes that have been changed, removed or added
while (attributesEnumeration.hasMore()) {
- Attribute oneAttr = attributesEnumeration.next();
+ NameAwareAttribute oneAttr = (NameAwareAttribute) attributesEnumeration.next();
collectModifications(oneAttr, tmpList);
}
@@ -313,9 +317,17 @@ public class DirContextAdapter implements DirContextOperations {
* @param modificationList the list in which to add the modifications.
* @throws NamingException if thrown by called Attribute methods.
*/
- private void collectModifications(Attribute changedAttr,
+ private void collectModifications(NameAwareAttribute changedAttr,
List modificationList) throws NamingException {
- Attribute currentAttribute = originalAttrs.get(changedAttr.getID());
+ NameAwareAttribute currentAttribute = originalAttrs.get(changedAttr.getID());
+ if(changedAttr.hasValuesAsNames()) {
+ try {
+ currentAttribute.initValuesAsNames();
+ } catch(IllegalArgumentException e) {
+ log.warn("Incompatible attributes; changed attribute has Name values but " +
+ "original cannot be converted to this");
+ }
+ }
if (changedAttr.equals(currentAttribute)) {
// No changes
@@ -368,15 +380,16 @@ public class DirContextAdapter implements DirContextOperations {
throws NamingException {
Attribute originalClone = (Attribute) originalAttr.clone();
- Attribute addedValuesAttribute = new BasicAttribute(originalAttr
+ Attribute addedValuesAttribute = new NameAwareAttribute(originalAttr
.getID());
- for (int i = 0; i < changedAttr.size(); i++) {
- Object attributeValue = changedAttr.get(i);
- if (!originalClone.remove(attributeValue)) {
- addedValuesAttribute.add(attributeValue);
- }
- }
+ NamingEnumeration> allValues = changedAttr.getAll();
+ while(allValues.hasMoreElements()) {
+ Object attributeValue = allValues.nextElement();
+ if (!originalClone.remove(attributeValue)) {
+ addedValuesAttribute.add(attributeValue);
+ }
+ }
// We have now traversed and removed all values from the original that
// were also present in the new values. The remaining values in the
@@ -552,19 +565,10 @@ public class DirContextAdapter implements DirContextOperations {
return originalAttrs.get(attrId) != null;
}
- /*
- * @see
- * org.springframework.ldap.support.DirContextOperations#getStringAttribute
- * (java.lang.String)
- */
public String getStringAttribute(String name) {
return (String) getObjectAttribute(name);
}
- /*
- * @see org.springframework.ldap.support.DirContextOperations#getObjectAttribute
- * (java.lang.String)
- */
public Object getObjectAttribute(String name) {
Attribute oneAttr = originalAttrs.get(name);
if (oneAttr == null || oneAttr.size() == 0) { // LDAP-215
@@ -579,19 +583,11 @@ public class DirContextAdapter implements DirContextOperations {
}
// LDAP-215
- /* (non-Javadoc)
- * @see org.springframework.ldap.core.DirContextOperations#attributeExists(java.lang.String)
- */
public boolean attributeExists(String name) {
Attribute oneAttr = originalAttrs.get(name);
return oneAttr != null;
}
- /*
- * @see
- * org.springframework.ldap.support.DirContextOperations#setAttributeValue
- * (java.lang.String, java.lang.Object)
- */
public void setAttributeValue(String name, Object value) {
// new entry
if (!updateMode && value != null) {
@@ -600,7 +596,7 @@ public class DirContextAdapter implements DirContextOperations {
// updating entry
if (updateMode) {
- BasicAttribute attribute = new BasicAttribute(name);
+ Attribute attribute = new NameAwareAttribute(name);
if (value != null) {
attribute.add(value);
}
@@ -608,13 +604,6 @@ public class DirContextAdapter implements DirContextOperations {
}
}
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.ldap.core.DirContextOperations#addAttributeValue(
- * java.lang.String, java.lang.Object)
- */
public void addAttributeValue(String name, Object value) {
addAttributeValue(name, value, DONT_ADD_IF_DUPLICATE_EXISTS);
}
@@ -654,13 +643,6 @@ public class DirContextAdapter implements DirContextOperations {
}
}
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.ldap.core.DirContextOperations#removeAttributeValue
- * (java.lang.String, java.lang.Object)
- */
public void removeAttributeValue(String name, Object value) {
if (!updateMode && value != null) {
Attribute attr = originalAttrs.get(name);
@@ -686,23 +668,13 @@ public class DirContextAdapter implements DirContextOperations {
}
}
- /*
- * @see
- * org.springframework.ldap.support.DirContextOperations#setAttributeValues
- * (java.lang.String, java.lang.Object[])
- */
public void setAttributeValues(String name, Object[] values) {
setAttributeValues(name, values, ORDER_DOESNT_MATTER);
}
- /*
- * @see
- * org.springframework.ldap.support.DirContextOperations#setAttributeValues
- * (java.lang.String, java.lang.Object[], boolean)
- */
public void setAttributeValues(String name, Object[] values,
boolean orderMatters) {
- Attribute a = new BasicAttribute(name, orderMatters);
+ Attribute a = new NameAwareAttribute(name, orderMatters);
for (int i = 0; values != null && i < values.length; i++) {
a.add(values[i]);
@@ -720,9 +692,6 @@ public class DirContextAdapter implements DirContextOperations {
}
}
- /*
- * @see org.springframework.ldap.support.DirContextOperations#update()
- */
public void update() {
NamingEnumeration extends Attribute> attributesEnumeration = null;
@@ -751,14 +720,9 @@ public class DirContextAdapter implements DirContextOperations {
}
// Reset the attributes to be updated
- updatedAttrs = new BasicAttributes(true);
+ updatedAttrs = new NameAwareAttributes();
}
- /*
- * @see
- * org.springframework.ldap.core.DirContextOperations#getStringAttributes
- * (java.lang.String)
- */
public String[] getStringAttributes(String name) {
try {
List objects = collectAttributeValuesAsList(name, String.class);
@@ -770,13 +734,6 @@ public class DirContextAdapter implements DirContextOperations {
}
}
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.ldap.core.DirContextOperations#getObjectAttributes
- * (java.lang.String)
- */
public Object[] getObjectAttributes(String name) {
try {
List list = collectAttributeValuesAsList(name, Object.class);
@@ -864,7 +821,7 @@ public class DirContextAdapter implements DirContextOperations {
throw new NameNotFoundException();
}
- Attributes a = new BasicAttributes(true);
+ Attributes a = new NameAwareAttributes();
Attribute target;
for (String attrId : attrIds) {
target = originalAttrs.get(attrId);
diff --git a/core/src/main/java/org/springframework/ldap/core/IterableNamingEnumeration.java b/core/src/main/java/org/springframework/ldap/core/IterableNamingEnumeration.java
new file mode 100644
index 00000000..3360fbae
--- /dev/null
+++ b/core/src/main/java/org/springframework/ldap/core/IterableNamingEnumeration.java
@@ -0,0 +1,40 @@
+package org.springframework.ldap.core;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import java.util.Iterator;
+
+/**
+* @author Mattias Hellborg Arthursson
+*/
+final class IterableNamingEnumeration implements NamingEnumeration {
+ private final Iterator iterator;
+
+ IterableNamingEnumeration(Iterable iterable) {
+ this.iterator = iterable.iterator();
+ }
+
+ @Override
+ public T next() {
+ return iterator.next();
+ }
+
+ @Override
+ public boolean hasMore() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public void close() throws NamingException {
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ return hasMore();
+ }
+
+ @Override
+ public T nextElement() {
+ return next();
+ }
+}
diff --git a/core/src/main/java/org/springframework/ldap/core/NameAwareAttribute.java b/core/src/main/java/org/springframework/ldap/core/NameAwareAttribute.java
new file mode 100644
index 00000000..f1ca4003
--- /dev/null
+++ b/core/src/main/java/org/springframework/ldap/core/NameAwareAttribute.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2005-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ldap.core;
+
+import org.springframework.ldap.InvalidNameException;
+import org.springframework.ldap.support.LdapUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+
+import javax.naming.Name;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.DirContext;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * Used internally to make DirContextAdapter properly handle Names as values.
+ *
+ * @author Mattias Hellborg Arthursson
+ * @since 2.0
+ */
+public class NameAwareAttribute implements Attribute {
+
+ private final String id;
+ private final boolean orderMatters;
+ private final Set values = new LinkedHashSet();
+ private Map valuesAsNames = new HashMap();
+
+ /**
+ * Construct a new instance with the specified id and one value.
+ * @param id the attribute id
+ * @param value the value to start off with
+ */
+ public NameAwareAttribute(String id, Object value) {
+ this(id);
+ values.add(value);
+ }
+
+ /**
+ * Construct a new instance from the supplied Attribute.
+ *
+ * @param attribute the Attribute to copy.
+ */
+ public NameAwareAttribute(Attribute attribute) {
+ this(attribute.getID(), attribute.isOrdered());
+ try {
+ NamingEnumeration> values = attribute.getAll();
+ while(values.hasMore()) {
+ this.add(values.next());
+ }
+ } catch (NamingException e) {
+ throw LdapUtils.convertLdapException(e);
+ }
+
+ if (attribute instanceof NameAwareAttribute) {
+ NameAwareAttribute nameAwareAttribute = (NameAwareAttribute) attribute;
+ populateValuesAsNames(nameAwareAttribute, this);
+ }
+ }
+
+ /**
+ * Construct a new instance with the specified id and no values.
+ * @param id the attribute id
+ */
+ public NameAwareAttribute(String id) {
+ this(id, false);
+ }
+
+ /**
+ * Construct a new instance with the specified id, no values and order significance as specified.
+ * @param id the attribute id
+ * @param orderMatters whether order has significance in this attribute.
+ */
+ public NameAwareAttribute(String id, boolean orderMatters) {
+ this.id = id;
+ this.orderMatters = orderMatters;
+ }
+
+ @Override
+ public NamingEnumeration> getAll() {
+ return new IterableNamingEnumeration(values);
+ }
+
+ @Override
+ public Object get() {
+ if(values.isEmpty()) {
+ return null;
+ }
+
+ return values.iterator().next();
+ }
+
+ @Override
+ public int size() {
+ return values.size();
+ }
+
+ @Override
+ public String getID() {
+ return id;
+ }
+
+ @Override
+ public boolean contains(Object attrVal) {
+ return values.contains(attrVal);
+ }
+
+ @Override
+ public boolean add(Object attrVal) {
+ if (attrVal instanceof Name) {
+ initValuesAsNames();
+
+ Name name = LdapUtils.newLdapName((Name) attrVal);
+ String currentValue = valuesAsNames.get(name);
+ String nameAsString = name.toString();
+ if(currentValue == null) {
+ valuesAsNames.put(name, name.toString());
+ values.add(nameAsString);
+ return true;
+ } else {
+ if(!currentValue.equals(nameAsString)) {
+ values.remove(currentValue);
+ values.add(nameAsString);
+ }
+
+ return false;
+ }
+ }
+
+ return values.add(attrVal);
+ }
+
+ public void initValuesAsNames() {
+ if(hasValuesAsNames()) {
+ return;
+ }
+
+ Map valuesAsNames = new HashMap();
+ for (Object value : values) {
+ if (value instanceof String) {
+ String s = (String) value;
+ try {
+ valuesAsNames.put(LdapUtils.newLdapName(s), s);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("This instance has values that are not valid distinguished names; " +
+ "cannot handle Name values");
+ }
+ } else {
+ throw new IllegalArgumentException("This instance has non-string attribute values; " +
+ "cannot handle Name values");
+ }
+ }
+
+ this.valuesAsNames = valuesAsNames;
+ }
+
+ public boolean hasValuesAsNames() {
+ return !valuesAsNames.isEmpty();
+ }
+
+ @Override
+ public boolean remove(Object attrval) {
+ if (attrval instanceof Name) {
+ initValuesAsNames();
+
+ Name name = LdapUtils.newLdapName((Name) attrval);
+ String removedValue = valuesAsNames.remove(name);
+ if(removedValue != null) {
+ values.remove(removedValue);
+
+ return true;
+ }
+
+ return false;
+ }
+ return values.remove(attrval);
+ }
+
+ @Override
+ public void clear() {
+ values.clear();
+ }
+
+ @Override
+ public DirContext getAttributeSyntaxDefinition() throws NamingException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DirContext getAttributeDefinition() throws NamingException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isOrdered() {
+ return orderMatters;
+ }
+
+ @Override
+ public Object get(int ix) throws NamingException {
+ Iterator iterator = values.iterator();
+
+ try {
+ Object value = iterator.next();
+ for(int i = 0; i < ix; i++) {
+ value = iterator.next();
+ }
+
+ return value;
+ } catch (NoSuchElementException e) {
+ throw new IndexOutOfBoundsException("No value at index i");
+ }
+ }
+
+ @Override
+ public Object remove(int ix) {
+ Iterator iterator = values.iterator();
+
+ try {
+ Object value = iterator.next();
+ for(int i = 0; i < ix; i++) {
+ value = iterator.next();
+ }
+
+ iterator.remove();
+ return value;
+ } catch (NoSuchElementException e) {
+ throw new IndexOutOfBoundsException("No value at index i");
+ }
+ }
+
+ @Override
+ public void add(int ix, Object attrVal) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object set(int ix, Object attrVal) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object clone() {
+ return new NameAwareAttribute(this);
+ }
+
+ private void populateValuesAsNames(NameAwareAttribute from, NameAwareAttribute to) {
+ Set> entries = from.valuesAsNames.entrySet();
+ for (Map.Entry entry : entries) {
+ to.valuesAsNames.put(LdapUtils.newLdapName(entry.getKey()), entry.getValue());
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NameAwareAttribute that = (NameAwareAttribute) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if(this.values.size() != that.values.size()) {
+ return false;
+ }
+
+ if(this.orderMatters != that.orderMatters || this.size() != that.size()) {
+ return false;
+ }
+
+ if(this.hasValuesAsNames() != that.hasValuesAsNames()) {
+ return false;
+ }
+
+ Set> myValues = this.values;
+ Set> theirValues = that.values;
+ if(this.hasValuesAsNames()) {
+ // We have Name values - compare these to get
+ // syntactically correct comparison of the values
+
+ myValues = this.valuesAsNames.keySet();
+ theirValues = that.valuesAsNames.keySet();
+ }
+
+ if(orderMatters) {
+ Iterator> thisIterator = myValues.iterator();
+ Iterator> thatIterator = theirValues.iterator();
+ while(thisIterator.hasNext()) {
+ if(!ObjectUtils.nullSafeEquals(thisIterator.next(), thatIterator.next())) {
+ return false;
+ }
+ }
+
+ return true;
+ } else {
+ for (Object value : myValues) {
+ if(!CollectionUtils.contains(theirValues.iterator(), value)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+
+ int valuesHash = 7;
+ Set> myValues = this.values;
+ if(hasValuesAsNames()) {
+ myValues = valuesAsNames.keySet();
+ }
+
+ for (Object value : myValues) {
+ result += ObjectUtils.nullSafeHashCode(value);
+ }
+ result = 31 * result + valuesHash;
+
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("NameAwareAttribute; id: %s; hasValuesAsNames: %s; orderMatters: %s; values: %s",
+ id, hasValuesAsNames(), orderMatters, values);
+ }
+}
diff --git a/core/src/main/java/org/springframework/ldap/core/NameAwareAttributes.java b/core/src/main/java/org/springframework/ldap/core/NameAwareAttributes.java
new file mode 100644
index 00000000..1f5c8334
--- /dev/null
+++ b/core/src/main/java/org/springframework/ldap/core/NameAwareAttributes.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2005-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ldap.core;
+
+import org.springframework.util.Assert;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Used internally to help DirContextAdapter properly handle Names as values.
+ *
+ * @author Mattias Hellborg Arthursson
+ * @since 2.0
+ */
+public class NameAwareAttributes implements Attributes {
+ private Map attributes = new HashMap();
+
+ /**
+ * Create an empty instance
+ */
+ public NameAwareAttributes() {
+
+ }
+
+ /**
+ * Create a new instance, populated with the data from the supplied instance.
+ * @param attributes the instance to copy.
+ */
+ public NameAwareAttributes(Attributes attributes) {
+ NamingEnumeration extends Attribute> allAttributes = attributes.getAll();
+ while(allAttributes.hasMoreElements()) {
+ Attribute attribute = allAttributes.nextElement();
+ put(new NameAwareAttribute(attribute));
+ }
+ }
+
+ @Override
+ public boolean isCaseIgnored() {
+ return true;
+ }
+
+ @Override
+ public int size() {
+ return attributes.size();
+ }
+
+ @Override
+ public NameAwareAttribute get(String attrID) {
+ Assert.hasLength(attrID, "Attribute ID must not be empty");
+ return attributes.get(attrID.toLowerCase());
+ }
+
+ @Override
+ public NamingEnumeration extends Attribute> getAll() {
+ return new IterableNamingEnumeration(attributes.values());
+ }
+
+ @Override
+ public NamingEnumeration getIDs() {
+ return new IterableNamingEnumeration(attributes.keySet());
+ }
+
+ @Override
+ public Attribute put(String attrID, Object val) {
+ Assert.hasLength(attrID, "Attribute ID must not be empty");
+ NameAwareAttribute newAttribute = new NameAwareAttribute(attrID, val);
+ attributes.put(attrID.toLowerCase(), newAttribute);
+
+ return newAttribute;
+ }
+
+ @Override
+ public Attribute put(Attribute attr) {
+ Assert.notNull(attr, "Attribute must not be null");
+ NameAwareAttribute newAttribute = new NameAwareAttribute(attr);
+ attributes.put(attr.getID().toLowerCase(), newAttribute);
+
+ return newAttribute;
+ }
+
+ @Override
+ public Attribute remove(String attrID) {
+ Assert.hasLength(attrID, "Attribute ID must not be empty");
+ return attributes.remove(attrID);
+ }
+
+ @Override
+ public Object clone() {
+ return new NameAwareAttributes(this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NameAwareAttributes that = (NameAwareAttributes) o;
+
+ if (attributes != null ? !attributes.equals(that.attributes) : that.attributes != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return attributes != null ? attributes.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("NameAwareAttribute; attributes: %s", attributes.toString());
+ }
+}
diff --git a/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java
index 811bf00e..e6819627 100755
--- a/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java
+++ b/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java
@@ -16,6 +16,7 @@
package org.springframework.ldap.odm.core.impl;
+import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.DnAttribute;
import org.springframework.ldap.odm.annotations.Id;
@@ -27,8 +28,13 @@ import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
/*
* Extract attribute meta-data from the @Attribute annotation, the @Id annotation
@@ -60,8 +66,10 @@ import java.util.Set;
private boolean isId;
// Is this field multi-valued represented by a List
- private boolean isList;
-
+ private boolean isCollection;
+
+ private Class extends Collection> collectionClass;
+
// Is this the objectClass attribute
private boolean isObjectClass;
@@ -112,18 +120,14 @@ import java.util.Set;
// Determine the class of data stored in the field
Class> fieldType = field.getType();
- // We support only lists for multi-valued attributes, as we must allow duplicate values
- if (Set.class.isAssignableFrom(fieldType)) {
- throw new MetaDataException(String.format("Only lists are allowed for multivlaued attributes, errpr in field %1$s in Entry class %2$s",
- field, field.getDeclaringClass()));
- }
- isList = List.class.isAssignableFrom(fieldType);
+ isCollection = Collection.class.isAssignableFrom(fieldType);
valueClass=null;
- if (!isList) {
+ if (!isCollection) {
// It's not a list so assume its single valued - so just take the field type
valueClass = fieldType;
} else {
+ determineCollectionClass(fieldType);
// It's multi-valued - so we need to look at the signature in
// the class file to find the generic type - this is supported for class file
// format 49 and greater which corresponds to java 5 and later.
@@ -156,7 +160,33 @@ import java.util.Set;
field, field.getDeclaringClass()));
}
}
-
+
+ @SuppressWarnings("unchecked")
+ private void determineCollectionClass(Class> fieldType) {
+ if(fieldType.isInterface()) {
+ if(Collection.class.equals(fieldType) || List.class.equals(fieldType)) {
+ collectionClass = ArrayList.class;
+ } else if(SortedSet.class.equals(fieldType)) {
+ collectionClass = TreeSet.class;
+ } else if(Set.class.isAssignableFrom(fieldType)) {
+ collectionClass = LinkedHashSet.class;
+ } else {
+ throw new MetaDataException(String.format("Collection class %s is not supported", fieldType));
+ }
+ } else {
+ collectionClass = (Class extends Collection>) fieldType;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection newCollectionInstance() {
+ try {
+ return (Collection) collectionClass.newInstance();
+ } catch (Exception e) {
+ throw new UncategorizedLdapException("Failed to instantiate collection class", e);
+ }
+ }
+
// Extract information from the @Id annotation:
// isId
private boolean processIdAnnotation(Field field, Class> fieldType) {
@@ -210,7 +240,7 @@ import java.util.Set;
}
// If this is the objectclass attribute then it must be of type List
- if (isObjectClass() && (!isList() || valueClass!=String.class)) {
+ if (isObjectClass() && (!isCollection() || valueClass!=String.class)) {
throw new MetaDataException(String.format("The type of the objectclass attribute must be List in classs %1$s",
field.getDeclaringClass()));
}
@@ -233,8 +263,8 @@ import java.util.Set;
return name;
}
- public boolean isList() {
- return isList;
+ public boolean isCollection() {
+ return isCollection;
}
public boolean isId() {
@@ -261,6 +291,16 @@ import java.util.Set;
return valueClass;
}
+ public Class> getJndiClass() {
+ if(isBinary()) {
+ return byte[].class;
+ } else if(Name.class.isAssignableFrom(valueClass)) {
+ return Name.class;
+ } else {
+ return String.class;
+ }
+ }
+
/*
* (non-Javadoc)
*
@@ -269,6 +309,6 @@ import java.util.Set;
@Override
public String toString() {
return String.format("name=%1$s | field=%2$s | valueClass=%3$s | syntax=%4$s| isBinary=%5$s | isId=%6$s | isList=%7$s | isObjectClass=%8$s",
- getName(), getField(), getValueClass().getName(), getSyntax(), isBinary(), isId(), isList(), isObjectClass());
+ getName(), getField(), getValueClass().getName(), getSyntax(), isBinary(), isId(), isCollection(), isObjectClass());
}
}
diff --git a/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java
index c0930d35..1fa5749d 100644
--- a/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java
+++ b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java
@@ -19,12 +19,14 @@ package org.springframework.ldap.odm.core.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.LdapDataEntry;
+import org.springframework.core.SpringVersion;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.odm.annotations.DnAttribute;
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
+import org.springframework.ldap.odm.typeconversion.impl.ConversionServiceConverterManager;
import org.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.ldap.support.LdapUtils;
@@ -66,7 +68,16 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
public DefaultObjectDirectoryMapper() {
- this.converterManager = new ConverterManagerImpl();
+ if(isAtLeast30()) {
+ this.converterManager = new ConversionServiceConverterManager();
+ } else {
+ this.converterManager = new ConverterManagerImpl();
+ }
+
+ }
+
+ private boolean isAtLeast30() {
+ return SpringVersion.getVersion().compareTo("3.0") > 0;
}
public void setConverterManager(ConverterManager converterManager) {
@@ -126,7 +137,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
for (Field field : metaData) {
AttributeMetaData attributeInfo = metaData.getAttribute(field);
if (!attributeInfo.isTransient() && !attributeInfo.isId() && !(attributeInfo.isObjectClass())) {
- Class> jndiClass = (attributeInfo.isBinary()) ? byte[].class : String.class;
+ Class> jndiClass = attributeInfo.getJndiClass();
Class> javaClass = attributeInfo.getValueClass();
if (!converterManager.canConvert(jndiClass, attributeInfo.getSyntax(), javaClass)) {
throw new InvalidEntryException(String.format(
@@ -184,9 +195,9 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
if (!attributeInfo.isTransient() && !attributeInfo.isId() && !(attributeInfo.isObjectClass())) {
try {
// If this is a "binary" object the JNDI expects a byte[] otherwise a String
- Class> targetClass = (attributeInfo.isBinary()) ? byte[].class : String.class;
+ Class> targetClass = attributeInfo.getJndiClass();
// Multi valued?
- if (!attributeInfo.isList()) {
+ if (!attributeInfo.isCollection()) {
populateSingleValueAttribute(entry, context, field, attributeInfo, targetClass);
} else {
@@ -204,7 +215,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
private void populateMultiValueAttribute(Object entry, LdapDataEntry context, Field field, AttributeMetaData attributeInfo, Class> targetClass) throws IllegalAccessException {
// We need to build up a list of of the values
- List attributeValues = new ArrayList();
+ List attributeValues = new ArrayList();
// Get the list of values
Collection> fieldValues = (Collection>)field.get(entry);
// Ignore null lists
@@ -212,7 +223,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
for (final Object o : fieldValues) {
// Ignore null values
if (o != null) {
- attributeValues.add((String)converterManager.convert(o, attributeInfo.getSyntax(),
+ attributeValues.add(converterManager.convert(o, attributeInfo.getSyntax(),
targetClass));
}
}
@@ -269,7 +280,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
Name dn = context.getDn();
if (!attributeInfo.isTransient() && !attributeInfo.isId()) {
// Not the ID - but is is multi valued?
- if (!attributeInfo.isList()) {
+ if (!attributeInfo.isCollection()) {
// No - its single valued, grab the JNDI attribute that corresponds to the metadata on the
// current field
populateSingleValueField(result, attributeValueMap, field, attributeInfo);
@@ -326,7 +337,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
private void populateMultiValueField(T result, Map attributeValueMap, Field field, AttributeMetaData attributeInfo) throws NamingException, IllegalAccessException {
// We need to build up a list of values
- List fieldValues = new ArrayList();
+ Collection fieldValues = attributeInfo.newCollectionInstance();
// Grab the attribute from the JNDI representation
Attribute currentAttribute = attributeValueMap.get(attributeInfo.getName());
// There is no guarantee that this attribute is present in the directory - so ignore nulls
diff --git a/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConversionServiceConverterManager.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConversionServiceConverterManager.java
new file mode 100644
index 00000000..2699bff8
--- /dev/null
+++ b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConversionServiceConverterManager.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2005-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ldap.odm.typeconversion.impl;
+
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.ldap.odm.typeconversion.ConverterManager;
+import org.springframework.ldap.support.LdapUtils;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+import javax.naming.Name;
+
+/**
+ * @author Mattias Hellborg Arthursson
+ * @since 2.0
+ */
+public class ConversionServiceConverterManager implements ConverterManager {
+ private GenericConversionService conversionService;
+ private final static String DEFAULT_CONVERSION_SERVICE_CLASS =
+ "org.springframework.core.convert.support.DefaultConversionService";
+
+ public ConversionServiceConverterManager(GenericConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public ConversionServiceConverterManager() {
+ ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader();
+ if(ClassUtils.isPresent(DEFAULT_CONVERSION_SERVICE_CLASS, defaultClassLoader)) {
+ try {
+ Class> clazz = ClassUtils.forName(DEFAULT_CONVERSION_SERVICE_CLASS, defaultClassLoader);
+ conversionService = (GenericConversionService) clazz.newInstance();
+ } catch (Exception e) {
+ ReflectionUtils.handleReflectionException(e);
+ }
+ } else {
+ conversionService = new GenericConversionService();
+ }
+
+ prePopulateWithNameConverter();
+ }
+
+ private void prePopulateWithNameConverter() {
+ conversionService.addConverter(new StringToNameConverter());
+ }
+
+ @Override
+ public boolean canConvert(Class> fromClass, String syntax, Class> toClass) {
+ return conversionService.canConvert(fromClass, toClass);
+ }
+
+ @Override
+ public T convert(Object source, String syntax, Class toClass) {
+ return conversionService.convert(source, toClass);
+ }
+
+ public final static class NameToStringConverter
+ implements org.springframework.core.convert.converter.Converter {
+ @Override
+ public String convert(Name source) {
+ if(source == null) {
+ return null;
+ }
+
+ return source.toString();
+ }
+ }
+
+ public final static class StringToNameConverter
+ implements org.springframework.core.convert.converter.Converter {
+
+ @Override
+ public Name convert(String source) {
+ if(source == null) {
+ return null;
+ }
+
+ return LdapUtils.newLdapName(source);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/StringConverter.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/StringConverter.java
new file mode 100644
index 00000000..ed364303
--- /dev/null
+++ b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/StringConverter.java
@@ -0,0 +1,7 @@
+package org.springframework.ldap.odm.typeconversion.impl;
+
+/**
+ * @author Mattias Hellborg Arthursson
+ */
+public class StringConverter {
+}
diff --git a/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java b/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java
index 548047b9..7a88ce26 100644
--- a/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java
+++ b/core/src/test/java/org/springframework/ldap/core/DirContextAdapterTest.java
@@ -42,7 +42,7 @@ import static org.junit.Assert.fail;
/**
* Tests the DirContextAdapter class.
- *
+ *
* @author Andreas Ronge
* @author Mattias Hellborg Arthursson
* @author Ulrik Sandberg
@@ -1114,7 +1114,7 @@ public class DirContextAdapterTest {
/**
* Test for LDAP-15: DirContextAdapter.setAttribute(). Verifies that setting
* an Attribute should modify updatedAttrs if in update mode.
- *
+ *
* @throws NamingException
*/
@Test
@@ -1223,4 +1223,133 @@ public class DirContextAdapterTest {
DirContextAdapter tested = new DirContextAdapter("cn=john doe, ou=company");
assertEquals(LdapUtils.newLdapName("cn=john doe, ou=company"), tested.getDn());
}
+
+ @Test
+ public void testAddDnAttributeValueIdentical() {
+ BasicAttributes attributes = new BasicAttributes();
+ attributes.put("uniqueMember", "cn=john doe, ou=company");
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.addAttributeValue("uniqueMember", LdapUtils.newLdapName("cn=john doe, ou=company"));
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(0, modificationItems.length);
+ }
+
+ @Test
+ public void testAddDnAttributeSyntacticallyEqual() {
+ BasicAttributes attributes = new BasicAttributes();
+ attributes.put("uniqueMember", "cn=john doe,OU=company");
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.addAttributeValue("uniqueMember", LdapUtils.newLdapName("cn=john doe, ou=company"));
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(0, modificationItems.length);
+ }
+
+ @Test
+ public void testRemoveDnAttributeSyntacticallyEqual() throws NamingException {
+ BasicAttributes attributes = new BasicAttributes();
+ attributes.put("uniqueMember", "cn=john doe,OU=company");
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.removeAttributeValue("uniqueMember", LdapUtils.newLdapName("cn=john doe, ou=company"));
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(1, modificationItems.length);
+
+ ModificationItem modificationItem = modificationItems[0];
+ assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItem.getModificationOp());
+ assertEquals("uniqueMember", modificationItem.getAttribute().getID());
+ }
+
+ @Test
+ public void testRemoveOneOfSeveralDnAttributeSyntacticallyEqual() throws NamingException {
+ BasicAttributes attributes = new BasicAttributes();
+ BasicAttribute attribute = new BasicAttribute("uniqueMember", "cn=john doe,OU=company");
+ attribute.add("cn=jane doe, ou=company");
+ attributes.put(attribute);
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.removeAttributeValue("uniqueMember", LdapUtils.newLdapName("cn=john doe, ou=company"));
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(1, modificationItems.length);
+
+ ModificationItem modificationItem = modificationItems[0];
+ assertEquals(DirContext.REMOVE_ATTRIBUTE, modificationItem.getModificationOp());
+ assertEquals("uniqueMember", modificationItem.getAttribute().getID());
+ assertEquals("cn=john doe,OU=company", modificationItem.getAttribute().get());
+ }
+
+ @Test
+ public void testAddDnAttributeNewValue() throws NamingException {
+ BasicAttributes attributes = new BasicAttributes();
+ attributes.put("uniqueMember", "cn=john doe, ou=company");
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.addAttributeValue("uniqueMember", LdapUtils.newLdapName("cn=jane doe, ou=company"));
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(1, modificationItems.length);
+
+ ModificationItem modificationItem = modificationItems[0];
+ assertEquals(DirContext.ADD_ATTRIBUTE, modificationItem.getModificationOp());
+ assertEquals("uniqueMember", modificationItem.getAttribute().getID());
+ assertEquals("cn=jane doe, ou=company", modificationItem.getAttribute().get());
+ }
+
+ @Test
+ public void testSetDnAttributeValueIdentical() {
+ BasicAttributes attributes = new BasicAttributes();
+ attributes.put("uniqueMember", "cn=john doe, ou=company");
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.setAttributeValue("uniqueMember", LdapUtils.newLdapName("cn=john doe, ou=company"));
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(0, modificationItems.length);
+ }
+
+ @Test
+ public void testSetDnAttributesValueIdentical() {
+ BasicAttributes attributes = new BasicAttributes();
+ attributes.put("uniqueMember", "cn=john doe, ou=company");
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.setAttributeValues("uniqueMember", new Object[]{LdapUtils.newLdapName("cn=john doe, ou=company")});
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(0, modificationItems.length);
+ }
+
+ @Test
+ public void testSetDnAttributesValuesOneNewEntry() throws NamingException {
+ BasicAttributes attributes = new BasicAttributes();
+ attributes.put("uniqueMember", "cn=john doe, ou=company");
+
+ DirContextAdapter tested = new DirContextAdapter(attributes, LdapUtils.newLdapName("cn=administrators, ou=groups"));
+ tested.setUpdateMode(true);
+
+ tested.setAttributeValues("uniqueMember", new Object[]{
+ LdapUtils.newLdapName("cn=john doe, ou=company"),
+ LdapUtils.newLdapName("cn=jane doe, ou=company")
+ });
+
+ ModificationItem[] modificationItems = tested.getModificationItems();
+ assertEquals(1, modificationItems.length);
+
+ ModificationItem modificationItem = modificationItems[0];
+ assertEquals(DirContext.ADD_ATTRIBUTE, modificationItem.getModificationOp());
+ assertEquals("uniqueMember", modificationItem.getAttribute().getID());
+ assertEquals("cn=jane doe, ou=company", modificationItem.getAttribute().get());
+ }
}
diff --git a/core/src/test/java/org/springframework/ldap/core/NameAwareAttributeTest.java b/core/src/test/java/org/springframework/ldap/core/NameAwareAttributeTest.java
new file mode 100644
index 00000000..8064827c
--- /dev/null
+++ b/core/src/test/java/org/springframework/ldap/core/NameAwareAttributeTest.java
@@ -0,0 +1,247 @@
+package org.springframework.ldap.core;
+
+import org.junit.Test;
+import org.springframework.ldap.support.LdapUtils;
+
+import javax.naming.NamingException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Mattias Hellborg Arthursson
+ */
+public class NameAwareAttributeTest {
+ @Test
+ public void testEqualsWithIdNotSame() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ NameAwareAttribute attr2 = new NameAwareAttribute("someOtherAttribute");
+
+ assertFalse(attr1.equals(attr2));
+ }
+
+ @Test
+ public void testEqualsWithSameIdNoValues() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+
+ assertTrue(attr1.equals(attr2));
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ }
+
+ @Test
+ public void testEqualsUnorderedWithIdenticalAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add("value1");
+ attr1.add("value2");
+ attr1.add("value3");
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add("value1");
+ attr2.add("value2");
+ attr2.add("value3");
+
+ assertTrue(attr1.equals(attr2));
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ }
+
+ @Test
+ public void testEqualsUnorderedWithIdenticalArrayAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(new byte[]{1, 2, 3});
+ attr1.add(new byte[]{3, 2, 1});
+ attr1.add(new byte[]{1});
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(new byte[]{1, 2, 3});
+ attr2.add(new byte[]{3, 2, 1});
+ attr2.add(new byte[]{1});
+
+ assertTrue(attr1.equals(attr2));
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ }
+
+ @Test
+ public void testEqualsUnorderedWithDifferentOrderArrayAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(new byte[]{1, 2, 3});
+ attr1.add(new byte[]{3, 2, 1});
+ attr1.add(new byte[]{1});
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(new byte[]{3, 2, 1});
+ attr2.add(new byte[]{1});
+ attr2.add(new byte[]{1, 2, 3});
+
+ assertTrue(attr1.equals(attr2));
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ }
+
+ @Test
+ public void testEqualsUnorderedWithDifferentArrayAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(new byte[]{1, 2, 3});
+ attr1.add(new byte[]{3, 2, 2});
+ attr1.add(new byte[]{1});
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(new byte[]{1, 2, 3});
+ attr2.add(new byte[]{3, 2, 1});
+ attr2.add(new byte[]{1});
+
+ assertFalse(attr1.equals(attr2));
+ }
+
+ @Test
+ public void testEqualsUnorderedWithDifferentNumberOfArrayAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(new byte[]{1, 2, 3});
+ attr1.add(new byte[]{3, 2, 1});
+ attr1.add(new byte[]{1});
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(new byte[]{1, 2, 3});
+ attr2.add(new byte[]{1});
+
+ assertFalse(attr1.equals(attr2));
+ }
+
+ @Test
+ public void testEqualsOrderedWithIdenticalArrayAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute", true);
+ attr1.add(new byte[]{1, 2, 3});
+ attr1.add(new byte[]{3, 2, 1});
+ attr1.add(new byte[]{1});
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute", true);
+ attr2.add(new byte[]{1, 2, 3});
+ attr2.add(new byte[]{3, 2, 1});
+ attr2.add(new byte[]{1});
+
+ assertTrue(attr1.equals(attr2));
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ }
+
+ @Test
+ public void testEqualsOrderedWithArrayAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute", true);
+ attr1.add(new byte[]{1, 2, 3});
+ attr1.add(new byte[]{3, 2, 1});
+ attr1.add(new byte[]{1});
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute", true);
+ attr2.add(new byte[]{1, 2, 3});
+ attr2.add(new byte[]{3, 2, 1});
+ attr2.add(new byte[]{1});
+
+ assertTrue(attr1.equals(attr2));
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ }
+
+ @Test
+ public void testEqualsOrderedWithDifferentOrderArrayAttributes() {
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute", true);
+ attr1.add(new byte[]{1, 2, 3});
+ attr1.add(new byte[]{3, 2, 1});
+ attr1.add(new byte[]{1});
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute", true);
+ attr2.add(new byte[]{3, 2, 1});
+ attr2.add(new byte[]{1});
+ attr2.add(new byte[]{1, 2, 3});
+
+ assertFalse(attr1.equals(attr2));
+ }
+
+ @Test
+ public void testSameDistinguishedNameValue() throws NamingException {
+ String expectedName = "cn=John Doe,ou=People";
+
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(LdapUtils.newLdapName(expectedName));
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(LdapUtils.newLdapName(expectedName));
+
+ assertEquals(attr1, attr2);
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ assertEquals(expectedName, attr1.get());
+ assertEquals(expectedName, attr2.get());
+ }
+
+ @Test
+ public void testEqualDistinguishedNameValue() throws NamingException {
+ // The names here are syntactically equal, but differ in exact string representation
+ String expectedName1 = "cn=John Doe, OU=People";
+ String expectedName2 = "cn=John Doe,ou=People";
+
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(LdapUtils.newLdapName(expectedName1));
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(LdapUtils.newLdapName(expectedName2));
+
+ assertEquals(attr1, attr2);
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ assertEquals(expectedName1, attr1.get());
+ assertEquals(expectedName2, attr2.get());
+ }
+
+ @Test
+ public void testEqualDistinguishedNameValueUninitialized() throws NamingException {
+ // The names here are syntactically equal, but differ in exact string representation
+ String expectedName1 = "cn=John Doe, OU=People";
+ String expectedName2 = "cn=John Doe,ou=People";
+
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(expectedName1);
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(LdapUtils.newLdapName(expectedName2));
+
+ assertFalse(attr1.equals(attr2));
+ assertEquals(expectedName1, attr1.get());
+ assertEquals(expectedName2, attr2.get());
+ }
+
+ @Test
+ public void testEqualDistinguishedNameValueManuallyInitialized() throws NamingException {
+ // The names here are syntactically equal, but differ in exact string representation
+ String expectedName1 = "cn=John Doe, OU=People";
+ String expectedName2 = "cn=John Doe,ou=People";
+
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(expectedName1);
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(LdapUtils.newLdapName(expectedName2));
+
+ attr1.initValuesAsNames();
+
+ assertTrue(attr1.equals(attr2));
+ assertEquals(attr1.hashCode(), attr2.hashCode());
+ assertEquals(expectedName1, attr1.get());
+ assertEquals(expectedName2, attr2.get());
+ }
+
+ @Test
+ public void testUnequalDistinguishedNameValue() throws NamingException {
+ // The names here are syntactically equal, but differ in exact string representation
+ String expectedName1 = "cn=Jane Doe,ou=People";
+ String expectedName2 = "cn=John Doe,ou=People";
+
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(LdapUtils.newLdapName(expectedName1));
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(LdapUtils.newLdapName(expectedName2));
+
+ assertFalse(attr1.equals(attr2));
+ assertEquals(expectedName1, attr1.get());
+ assertEquals(expectedName2, attr2.get());
+ }
+
+ @Test
+ public void testComparingWDistinguishedNameValueWithInvalidName() throws NamingException {
+ // The names here are syntactically equal, but differ in exact string representation
+ String expectedName1 = "cn=Jane Doe,ou=People";
+ String expectedValue2 = "thisisnotavaliddn";
+
+ NameAwareAttribute attr1 = new NameAwareAttribute("someAttribute");
+ attr1.add(LdapUtils.newLdapName(expectedName1));
+ NameAwareAttribute attr2 = new NameAwareAttribute("someAttribute");
+ attr2.add(expectedValue2);
+
+ assertFalse(attr1.equals(attr2));
+ assertEquals(expectedName1, attr1.get());
+ assertEquals(expectedValue2, attr2.get());
+ }
+}
diff --git a/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java b/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java
index 61a0ad8c..8979c814 100644
--- a/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java
+++ b/core/src/test/java/org/springframework/ldap/core/support/DefaultDirObjectFactoryTest.java
@@ -18,12 +18,14 @@ package org.springframework.ldap.core.support;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.NameAwareAttributes;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
+import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import java.util.Hashtable;
@@ -54,7 +56,7 @@ public class DefaultDirObjectFactoryTest {
@Test
public void testGetObjectInstance() throws Exception {
- BasicAttributes expectedAttributes = new BasicAttributes();
+ Attributes expectedAttributes = new NameAwareAttributes();
expectedAttributes.put("someAttribute", "someValue");
DirContextAdapter adapter = (DirContextAdapter) tested.getObjectInstance(contextMock, DN, null,
@@ -68,7 +70,7 @@ public class DefaultDirObjectFactoryTest {
@Test
public void testGetObjectInstance_CompositeName() throws Exception {
- BasicAttributes expectedAttributes = new BasicAttributes();
+ Attributes expectedAttributes = new NameAwareAttributes();
expectedAttributes.put("someAttribute", "someValue");
CompositeName name = new CompositeName();
@@ -85,7 +87,7 @@ public class DefaultDirObjectFactoryTest {
@Test
public void testGetObjectInstance_nullObject() throws Exception {
- BasicAttributes expectedAttributes = new BasicAttributes();
+ Attributes expectedAttributes = new NameAwareAttributes();
expectedAttributes.put("someAttribute", "someValue");
DirContextAdapter adapter = (DirContextAdapter) tested.getObjectInstance(null, DN, null, new Hashtable(),
@@ -97,7 +99,7 @@ public class DefaultDirObjectFactoryTest {
@Test
public void testGetObjectInstance_ObjectNotContext() throws Exception {
- BasicAttributes expectedAttributes = new BasicAttributes();
+ Attributes expectedAttributes = new NameAwareAttributes();
expectedAttributes.put("someAttribute", "someValue");
DirContextAdapter adapter = (DirContextAdapter) tested.getObjectInstance(new Object(), DN, null,
@@ -114,7 +116,7 @@ public class DefaultDirObjectFactoryTest {
*/
@Test
public void testGetObjectInstance_BaseSet() throws Exception {
- BasicAttributes expectedAttributes = new BasicAttributes();
+ Attributes expectedAttributes = new NameAwareAttributes();
expectedAttributes.put("someAttribute", "someValue");
when(contextMock2.getNameInNamespace()).thenReturn("dc=jayway, dc=se");
diff --git a/core/src/test/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapperTest.java b/core/src/test/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapperTest.java
index caff8a8b..1116ebfc 100644
--- a/core/src/test/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapperTest.java
+++ b/core/src/test/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapperTest.java
@@ -51,7 +51,7 @@ public class DefaultObjectDirectoryMapperTest {
assertFalse(idAttribute.isBinary());
assertFalse(idAttribute.isDnAttribute());
assertFalse(idAttribute.isTransient());
- assertFalse(idAttribute.isList());
+ assertFalse(idAttribute.isCollection());
assertField(entityData, "fullName", "cn", "cn", false, false, false);
assertField(entityData, "lastName", "sn", null, false, false, false);
@@ -115,7 +115,7 @@ public class DefaultObjectDirectoryMapperTest {
assertEquals(expectedBinary, attribute.isBinary());
assertEquals(expectedTransient, attribute.isTransient());
- assertEquals(expectedList, attribute.isList());
+ assertEquals(expectedList, attribute.isCollection());
}
}
}
diff --git a/gradle/java.gradle b/gradle/java.gradle
index 75085ca5..57213554 100644
--- a/gradle/java.gradle
+++ b/gradle/java.gradle
@@ -15,6 +15,7 @@ ext.gsbaseVersion = '2.0.1'
ext.log4jVersion = '1.2.15'
ext.mockitoVersion = '1.9.5'
ext.queryDslVersion = '3.2.4'
+ext.slf4jVersion = '1.7.5'
repositories {
mavenCentral()
diff --git a/test/integration-tests-ad/build.gradle b/test/integration-tests-ad/build.gradle
index c296451a..d408f7df 100644
--- a/test/integration-tests-ad/build.gradle
+++ b/test/integration-tests-ad/build.gradle
@@ -9,7 +9,9 @@ dependencies {
"org.springframework:spring-orm:$springVersion"
testCompile "org.springframework:spring-test:$springVersion",
- "junit:junit:$junitVersion"
+ "junit:junit:$junitVersion",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
+
}
test.enabled = false // TODO this should be enabled depending on build parameter
\ No newline at end of file
diff --git a/test/integration-tests-openldap/build.gradle b/test/integration-tests-openldap/build.gradle
index e6572394..d143209b 100644
--- a/test/integration-tests-openldap/build.gradle
+++ b/test/integration-tests-openldap/build.gradle
@@ -13,7 +13,9 @@ dependencies {
"org.springframework:spring-test:$springVersion",
"gsbase:gsbase:$gsbaseVersion",
"junit:junit:$junitVersion",
- "com.sun:ldapbp:1.0"
+ "com.sun:ldapbp:1.0",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
+
}
test.enabled = false // TODO this should be enabled depending on build parameter
\ No newline at end of file
diff --git a/test/integration-tests-spring20/build.gradle b/test/integration-tests-spring20/build.gradle
index b7b56899..153e2145 100644
--- a/test/integration-tests-spring20/build.gradle
+++ b/test/integration-tests-spring20/build.gradle
@@ -18,5 +18,7 @@ dependencies {
"org.springframework:spring-context:$spring20Version",
"org.springframework:spring-core:$spring20Version",
"org.springframework:spring-dao:$spring20Version",
- "org.springframework:spring-beans:$spring20Version"
+ "org.springframework:spring-beans:$spring20Version",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
+
}
\ No newline at end of file
diff --git a/test/integration-tests-spring25/build.gradle b/test/integration-tests-spring25/build.gradle
index 2cdf23d8..95a5330f 100644
--- a/test/integration-tests-spring25/build.gradle
+++ b/test/integration-tests-spring25/build.gradle
@@ -23,5 +23,7 @@ dependencies {
"org.springframework:spring-core:$spring25Version",
"org.springframework:spring-tx:$spring25Version",
"org.springframework:spring-beans:$spring25Version",
- "junit:junit:$junitVersion"
+ "junit:junit:$junitVersion",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
+
}
diff --git a/test/integration-tests-spring30/build.gradle b/test/integration-tests-spring30/build.gradle
index 859864f4..290d3e83 100644
--- a/test/integration-tests-spring30/build.gradle
+++ b/test/integration-tests-spring30/build.gradle
@@ -23,5 +23,6 @@ dependencies {
"org.springframework:spring-core:$spring30Version",
"org.springframework:spring-tx:$spring30Version",
"org.springframework:spring-beans:$spring30Version",
- "junit:junit:$junitVersion"
+ "junit:junit:$junitVersion",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
}
diff --git a/test/integration-tests-sunone/build.gradle b/test/integration-tests-sunone/build.gradle
index 650dc54f..9b859b4d 100644
--- a/test/integration-tests-sunone/build.gradle
+++ b/test/integration-tests-sunone/build.gradle
@@ -13,7 +13,9 @@ dependencies {
testCompile "junit:junit:$junitVersion",
"org.springframework:spring-aop:$springVersion",
"org.springframework:spring-aop:$springVersion",
- "gsbase:gsbase:$gsbaseVersion"
+ "gsbase:gsbase:$gsbaseVersion",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
+
}
test.enabled = false // TODO this should be enabled depending on build parameter
\ No newline at end of file
diff --git a/test/integration-tests/build.gradle b/test/integration-tests/build.gradle
index fed82ee5..d2f09c96 100644
--- a/test/integration-tests/build.gradle
+++ b/test/integration-tests/build.gradle
@@ -46,7 +46,9 @@ dependencies {
"aspectj:aspectjweaver:1.5.3",
"hsqldb:hsqldb:1.8.0.7",
"junit:junit:$junitVersion",
- "org.springframework.security:spring-security-config:$springSecurityVersion"
+ "org.springframework.security:spring-security-config:$springSecurityVersion",
+ "org.slf4j:slf4j-log4j12:$slf4jVersion"
+
testCompile("org.springframework.security:spring-security-ldap:$springSecurityVersion") {
exclude group: "org.springframework.ldap", module: "spring-ldap-core"
diff --git a/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Group.java b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Group.java
new file mode 100644
index 00000000..82e8d1b9
--- /dev/null
+++ b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Group.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2005-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ldap.itest.odm;
+
+import org.springframework.ldap.odm.annotations.Attribute;
+import org.springframework.ldap.odm.annotations.DnAttribute;
+import org.springframework.ldap.odm.annotations.Entry;
+import org.springframework.ldap.odm.annotations.Id;
+
+import javax.naming.Name;
+import java.util.Set;
+
+/**
+ * @author Mattias Hellborg Arthursson
+ */
+@Entry(objectClasses = {"top", "groupOfUniqueNames"}, base = "cn=groups")
+public class Group {
+
+ @Id
+ private Name dn;
+
+ @Attribute(name="cn")
+ @DnAttribute("cn")
+ private String name;
+
+ @Attribute(name="uniqueMember")
+ private Set members;
+
+ public Name getDn() {
+ return dn;
+ }
+
+ public void setDn(Name dn) {
+ this.dn = dn;
+ }
+
+ public Set getMembers() {
+ return members;
+ }
+
+ public void setMembers(Set members) {
+ this.members = members;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void addMember(Name member) {
+ members.add(member);
+ }
+
+ public void removeMember(Name member) {
+ members.remove(member);
+ }
+}
diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateBindUnbindITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateBindUnbindITest.java
index be5f0768..4dca689c 100644
--- a/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateBindUnbindITest.java
+++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateBindUnbindITest.java
@@ -58,6 +58,16 @@ public class LdapTemplateBindUnbindITest extends
verifyCleanup();
}
+ @Test
+ public void testBindGroupOfUniqueNamesWithNameValues() {
+ DirContextAdapter ctx = new DirContextAdapter(LdapUtils.newLdapName("cn=TEST,ou=groups"));
+ ctx.addAttributeValue("cn", "TEST");
+ ctx.addAttributeValue("objectclass", "top");
+ ctx.addAttributeValue("objectclass", "groupOfUniqueNames");
+ ctx.addAttributeValue("uniqueMember", LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se"));
+ tested.bind(ctx);
+ }
+
@Test
public void testBindAndUnbindWithAttributesUsingLdapName() {
Attributes attributes = setupAttributes();
diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmGroupManipulationITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmGroupManipulationITest.java
new file mode 100644
index 00000000..a3c7e381
--- /dev/null
+++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmGroupManipulationITest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2005-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ldap.itest.odm;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.itest.AbstractLdapTemplateIntegrationTest;
+import org.springframework.ldap.support.LdapUtils;
+import org.springframework.test.context.ContextConfiguration;
+
+import javax.naming.Name;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.ldap.query.LdapQueryBuilder.query;
+
+/**
+ * @author Mattias Hellborg Arthursson
+ */
+@ContextConfiguration(locations = {"/conf/ldapTemplateTestContext.xml"})
+public class LdapTemplateOdmGroupManipulationITest extends AbstractLdapTemplateIntegrationTest {
+ @Autowired
+ private LdapTemplate tested;
+
+ @Test
+ public void testFindOne() {
+ Group group = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ assertNotNull(group);
+ assertEquals("ROLE_USER", group.getName());
+ assertEquals(5, group.getMembers().size());
+
+ Set members = group.getMembers();
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Norway,dc=jayway,dc=se")));
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company2,c=Sweden,dc=jayway,dc=se")));
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person3,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ }
+
+ @Test
+ public void testRemoveMember() {
+ Group group = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ group.removeMember(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se"));
+ tested.update(group);
+
+ Group verification = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ Set members = verification.getMembers();
+ assertEquals(4, members.size());
+ assertFalse(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ }
+
+ @Test
+ public void testRemoveMemberSyntacticallyEqual() {
+ Group group = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ group.removeMember(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden, DC=jayway,DC=se"));
+ tested.update(group);
+
+ Group verification = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ Set members = verification.getMembers();
+ assertEquals(4, members.size());
+ assertFalse(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ }
+
+ @Test
+ public void testAddMember() {
+ Group group = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ group.addMember(LdapUtils.newLdapName("cn=Some Person+sn=Person,ou=company1,c=Norway,dc=jayway,dc=se"));
+ tested.update(group);
+
+ Group verification = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ Set members = verification.getMembers();
+ assertEquals(6, members.size());
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person+sn=Person,ou=company1,c=Norway,dc=jayway,dc=se")));
+ }
+
+ @Test
+ public void testAddMemberDuplicate() {
+ Group group = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ group.addMember(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se"));
+ tested.update(group);
+
+ Group verification = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ Set members = verification.getMembers();
+ assertEquals(5, members.size());
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ }
+
+ @Test
+ public void testAddMemberSyntacticallyEqualDuplicate() {
+ Group group = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ group.addMember(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,DC=jayway,DC=se"));
+ tested.update(group);
+
+ Group verification = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ Set members = verification.getMembers();
+ assertEquals(5, members.size());
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ }
+
+ @Test
+ public void testSetMembersSyntacticallyEqual() {
+ Group group = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ group.setMembers(new HashSet(){{
+ add(LdapUtils.newLdapName("CN=Some Person,OU=company1, C=Sweden, DC=jayway,DC=se"));
+ add(LdapUtils.newLdapName("CN=Some Person2, OU=company1,C=Sweden,DC=jayway, DC=se"));
+ }});
+ tested.update(group);
+
+ Group verification = tested.findOne(query().where("cn").is("ROLE_USER"), Group.class);
+
+ Set members = verification.getMembers();
+
+ assertEquals(2, members.size());
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ assertTrue(members.contains(LdapUtils.newLdapName("cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se")));
+ }
+}