From c2abc9660e39b8a01ad12028b75f723537d67de6 Mon Sep 17 00:00:00 2001 From: Mattias Hellborg Arthursson Date: Thu, 24 Oct 2013 10:25:22 +0200 Subject: [PATCH] LDAP-248: DirContextAdapter now handles javax.naming.Name instances properly with regards to equality LDAP-274: ODM now uses standard Spring converter (if present). LDAP-275: Support for different Collection types in ODM --- core/build.gradle | 4 +- .../org/springframework/LdapDataEntry.java | 34 ++ .../ldap/core/DirContextAdapter.java | 145 +++----- .../ldap/core/IterableNamingEnumeration.java | 40 ++ .../ldap/core/NameAwareAttribute.java | 348 ++++++++++++++++++ .../ldap/core/NameAwareAttributes.java | 131 +++++++ .../ldap/odm/core/impl/AttributeMetaData.java | 68 +++- .../impl/DefaultObjectDirectoryMapper.java | 27 +- .../ConversionServiceConverterManager.java | 95 +++++ .../typeconversion/impl/StringConverter.java | 7 + .../ldap/core/DirContextAdapterTest.java | 133 ++++++- .../ldap/core/NameAwareAttributeTest.java | 247 +++++++++++++ .../support/DefaultDirObjectFactoryTest.java | 12 +- .../DefaultObjectDirectoryMapperTest.java | 4 +- gradle/java.gradle | 1 + test/integration-tests-ad/build.gradle | 4 +- test/integration-tests-openldap/build.gradle | 4 +- test/integration-tests-spring20/build.gradle | 4 +- test/integration-tests-spring25/build.gradle | 4 +- test/integration-tests-spring30/build.gradle | 3 +- test/integration-tests-sunone/build.gradle | 4 +- test/integration-tests/build.gradle | 4 +- .../springframework/ldap/itest/odm/Group.java | 74 ++++ .../itest/LdapTemplateBindUnbindITest.java | 10 + ...LdapTemplateOdmGroupManipulationITest.java | 148 ++++++++ 25 files changed, 1422 insertions(+), 133 deletions(-) create mode 100644 core/src/main/java/org/springframework/ldap/core/IterableNamingEnumeration.java create mode 100644 core/src/main/java/org/springframework/ldap/core/NameAwareAttribute.java create mode 100644 core/src/main/java/org/springframework/ldap/core/NameAwareAttributes.java create mode 100644 core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConversionServiceConverterManager.java create mode 100644 core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/StringConverter.java create mode 100644 core/src/test/java/org/springframework/ldap/core/NameAwareAttributeTest.java create mode 100644 test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Group.java create mode 100644 test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmGroupManipulationITest.java 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 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 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 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 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) 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"))); + } +}