From 78b9617bf18a3a78b78bf7e1f8a6c8900f6ae077 Mon Sep 17 00:00:00 2001 From: Mattias Hellborg Arthursson Date: Sun, 29 Sep 2013 16:11:14 +0200 Subject: [PATCH] LDAP-265, LDAP-268: Added the ability to automatically map to/from DN values in ODM. Added some much needed unit tests for ODM functionality. --- .../ldap/core/LdapTemplate.java | 50 +++- .../ldap/odm/annotations/DnAttribute.java | 16 ++ .../ldap/odm/annotations/Entry.java | 8 + .../ldap/odm/core/ObjectDirectoryMapper.java | 2 + .../ldap/odm/core/impl/AttributeMetaData.java | 48 +++- .../impl/DefaultObjectDirectoryMapper.java | 203 ++++++++++------ .../ldap/odm/core/impl/ObjectMetaData.java | 107 +++++++-- .../ldap/core/LdapTemplateLookupTest.java | 27 ++- .../ldap/core/LdapTemplateOdmTest.java | 34 +++ .../ldap/core/LdapTemplateTest.java | 224 ++++++++++++++++-- .../DefaultObjectDirectoryMapperTest.java | 122 ++++++++++ .../ldap/odm/core/impl/UnitTestPerson.java | 57 +++++ ...onWithIndexedAndUnindexedDnAttributes.java | 54 +++++ ...UnitTestPersonWithIndexedDnAttributes.java | 53 +++++ .../UnitTestPersonWithInvalidFieldType.java | 36 +++ samples/odm/readme.txt | 2 +- .../dao/OdmPersonDaoImpl.java} | 28 +-- .../ldap/samples/plain/domain/Person.java | 6 +- .../src/main/resources/applicationContext.xml | 2 +- .../dao/PersonDaoSampleIntegrationTest.java | 8 +- .../src/test/resources/config/testContext.xml | 2 +- .../ldap/samples/plain/dao/PersonDaoImpl.java | 2 +- .../itest/odm/PersonWithDnAnnotations.java | 107 +++++++++ ...LdapTemplateOdmWithDnAnnotationsITest.java | 161 +++++++++++++ ...pTemplateOdmWithNoDnAnnotationsITest.java} | 2 +- 25 files changed, 1212 insertions(+), 149 deletions(-) create mode 100644 core/src/main/java/org/springframework/ldap/odm/annotations/DnAttribute.java create mode 100644 core/src/test/java/org/springframework/ldap/core/LdapTemplateOdmTest.java create mode 100644 core/src/test/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapperTest.java create mode 100644 core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPerson.java create mode 100644 core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedAndUnindexedDnAttributes.java create mode 100644 core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedDnAttributes.java create mode 100644 core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithInvalidFieldType.java rename samples/odm/src/main/java/org/springframework/ldap/samples/{plain/dao/PersonDaoImpl.java => odm/dao/OdmPersonDaoImpl.java} (70%) rename samples/odm/src/test/java/org/springframework/ldap/samples/{plain => odm}/dao/PersonDaoSampleIntegrationTest.java (93%) create mode 100644 test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/PersonWithDnAnnotations.java create mode 100644 test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmWithDnAnnotationsITest.java rename test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/{LdapTemplateOdmITest.java => LdapTemplateOdmWithNoDnAnnotationsITest.java} (98%) diff --git a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java index e5642ff2..68e642a0 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -1736,8 +1736,8 @@ public class LdapTemplate implements LdapOperations, InitializingBean { log.debug(String.format("Reading Entry at - %s$1", dn)); } - // TODO: validate class before lookup - // getEntityData(clazz); + // Make sure the class is OK before doing the lookup + odm.manageClass(clazz); T result = lookup(dn, new ContextMapper() { @Override @@ -1758,11 +1758,20 @@ public class LdapTemplate implements LdapOperations, InitializingBean { @Override public void create(Object entry) { + Assert.notNull(entry, "Entry must not be null"); + if (log.isDebugEnabled()) { log.debug(String.format("Creating entry - %s$1", entry)); } - DirContextAdapter context = new DirContextAdapter(odm.getId(entry)); + Name id = odm.getId(entry); + if(id == null) { + id = odm.getCalculatedId(entry); + } + + Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); + + DirContextAdapter context = new DirContextAdapter(id); odm.mapToLdapDataEntry(entry, context); bind(context); @@ -1770,13 +1779,42 @@ public class LdapTemplate implements LdapOperations, InitializingBean { @Override public void update(Object entry) { + Assert.notNull(entry, "Entry must not be null"); if (log.isDebugEnabled()) { log.debug(String.format("Updating entry - %s$1", entry)); } - DirContextOperations context = lookupContext(odm.getId(entry)); - odm.mapToLdapDataEntry(entry, context); - modifyAttributes(context); + Name originalId = odm.getId(entry); + Name calculatedId = odm.getCalculatedId(entry); + + if(originalId != null && calculatedId != null && !originalId.equals(calculatedId)) { + // The DN has changed - remove the original entry and bind the new one + // (because other data may have changed as well + if (log.isDebugEnabled()) { + log.debug(String.format("Calculated DN of %s; of entry %s differs from explicitly specified one; %s - moving", + calculatedId, entry, originalId)); + } + + unbind(originalId); + + DirContextAdapter context = new DirContextAdapter(calculatedId); + odm.mapToLdapDataEntry(entry, context); + + bind(context); + } else { + // DN is the same, just modify the attributes + + Name id = originalId; + if(id == null) { + id = calculatedId; + } + + Assert.notNull(id, String.format("Unable to determine id for entry %s", entry.toString())); + + DirContextOperations context = lookupContext(id); + odm.mapToLdapDataEntry(entry, context); + modifyAttributes(context); + } } @Override diff --git a/core/src/main/java/org/springframework/ldap/odm/annotations/DnAttribute.java b/core/src/main/java/org/springframework/ldap/odm/annotations/DnAttribute.java new file mode 100644 index 00000000..1167f49e --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/odm/annotations/DnAttribute.java @@ -0,0 +1,16 @@ +package org.springframework.ldap.odm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Mattias Hellborg Arthursson + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DnAttribute { + String value(); + int index() default -1; +} diff --git a/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java b/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java index 3de360bc..9fcf30da 100755 --- a/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java +++ b/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java @@ -22,4 +22,12 @@ public @interface Entry { * @return A list of LDAP classes which the annotated Java class represents. */ String[] objectClasses(); + + /** + * The base DN of this entry. If specified, this will be prepended to all calculated + * distinguished names for entries of the annotated class. + * + * @return the base DN for entries of this class + */ + String base() default ""; } diff --git a/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java index 719baaa9..2098ac09 100644 --- a/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java +++ b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java @@ -56,6 +56,8 @@ public interface ObjectDirectoryMapper { */ Name getId(Object entry); + Name getCalculatedId(Object entry); + /** * Use the specified search filter and return a new one that only applies to entries of the specified class. * In effect this means padding the original filter with an objectclass condition. 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 ccf032a3..ff7c4da9 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 @@ -1,5 +1,11 @@ package org.springframework.ldap.odm.core.impl; +import org.springframework.ldap.odm.annotations.Attribute; +import org.springframework.ldap.odm.annotations.DnAttribute; +import org.springframework.ldap.odm.annotations.Id; +import org.springframework.ldap.odm.annotations.Transient; + +import javax.naming.Name; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; @@ -8,11 +14,6 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Set; -import javax.naming.Name; - -import org.springframework.ldap.odm.annotations.Attribute; -import org.springframework.ldap.odm.annotations.Id; - /* * Extract attribute meta-data from the @Attribute annotation, the @Id annotation * and via reflection. @@ -47,7 +48,11 @@ import org.springframework.ldap.odm.annotations.Id; // Is this the objectClass attribute private boolean isObjectClass; - + + private boolean isTransient = false; + + private DnAttribute dnAttribute; + // Extract information from the @Attribute annotation: // syntax, isBinary, isObjectClass and name. private boolean processAttributeAnnotation(Field field) { @@ -157,10 +162,24 @@ import org.springframework.ldap.odm.annotations.Id; // Extract meta-data from the given field public AttributeMetaData(Field field) { this.field=field; - + + this.dnAttribute = field.getAnnotation(DnAttribute.class); + if(this.dnAttribute != null && !field.getType().equals(String.class)) { + throw new MetaDataException(String.format("%s is of type %s, but only String attributes can be declared as @DnAttributes", + field.toString(), + field.getType().toString())); + } + + Transient transientAnnotation = field.getAnnotation(Transient.class); + if(transientAnnotation != null) { + this.isTransient = true; + return; + } + // Reflection data determineFieldType(field); - + + // Data from the @Attribute annotation boolean foundAttributeAnnotation=processAttributeAnnotation(field); @@ -181,6 +200,7 @@ import org.springframework.ldap.odm.annotations.Id; } } + public String getSyntax() { return syntax; } @@ -205,6 +225,18 @@ import org.springframework.ldap.odm.annotations.Id; return isId; } + public boolean isTransient() { + return isTransient; + } + + public DnAttribute getDnAttribute() { + return dnAttribute; + } + + public boolean isDnAttribute() { + return dnAttribute != null; + } + public boolean isObjectClass() { return 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 dbd7de5f..066b1aea 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 @@ -22,9 +22,14 @@ import org.springframework.LdapDataEntry; 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.ConverterManagerImpl; +import org.springframework.ldap.support.LdapNameBuilder; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; import javax.naming.Name; import javax.naming.NamingEnumeration; @@ -68,9 +73,9 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { this.converterManager = converterManager; } - private static final class EntityData { - private final ObjectMetaData metaData; - private final Filter ocFilter; + static final class EntityData { + final ObjectMetaData metaData; + final Filter ocFilter; private EntityData(ObjectMetaData metaData, Filter ocFilter) { this.metaData=metaData; @@ -120,7 +125,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { // Check we have all of the necessary converters for the class for (Field field : metaData) { AttributeMetaData attributeInfo = metaData.getAttribute(field); - if (!attributeInfo.isId() && !(attributeInfo.isObjectClass())) { + if (!attributeInfo.isTransient() && !attributeInfo.isId() && !(attributeInfo.isObjectClass())) { Class jndiClass = (attributeInfo.isBinary()) ? byte[].class : String.class; Class javaClass = attributeInfo.getValueClass(); if (!converterManager.canConvert(jndiClass, attributeInfo.getSyntax(), javaClass)) { @@ -176,36 +181,18 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { // Grab the meta data for the current field AttributeMetaData attributeInfo = metaData.getAttribute(field); // We dealt with the object class field about, and the DN is set by the call to write the object to LDAP - if (!attributeInfo.isId() && !(attributeInfo.isObjectClass())) { + 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; // Multi valued? if (!attributeInfo.isList()) { - // Single valued - get the value of the field - Object fieldValue = field.get(entry); - // Ignore null field values - if (fieldValue != null) { - // Convert the field value to the required type and write it into the JNDI context - context.setAttributeValue(attributeInfo.getName().toString(), converterManager.convert(fieldValue, - attributeInfo.getSyntax(), targetClass)); - } - } else { // Multi-valued - // We need to build up a list of of the values - List attributeValues = new ArrayList(); - // Get the list of values - Collection fieldValues = (Collection)field.get(entry); - // Ignore null lists - if (fieldValues != null) { - for (final Object o : fieldValues) { - // Ignore null values - if (o != null) { - attributeValues.add((String)converterManager.convert(o, attributeInfo.getSyntax(), - targetClass)); - } - } - context.setAttributeValues(attributeInfo.getName().toString(), attributeValues.toArray()); - } + populateSingleValueAttribute(entry, context, field, attributeInfo, targetClass); + + } else { + // Multi-valued + populateMultiValueAttribute(entry, context, field, attributeInfo, targetClass); + } } catch (IllegalAccessException e) { throw new InvalidEntryException(String.format("Can't set attribute %1$s", attributeInfo.getName()), @@ -215,6 +202,35 @@ 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(); + // Get the list of values + Collection fieldValues = (Collection)field.get(entry); + // Ignore null lists + if (fieldValues != null) { + for (final Object o : fieldValues) { + // Ignore null values + if (o != null) { + attributeValues.add((String)converterManager.convert(o, attributeInfo.getSyntax(), + targetClass)); + } + } + context.setAttributeValues(attributeInfo.getName().toString(), attributeValues.toArray()); + } + } + + private void populateSingleValueAttribute(Object entry, LdapDataEntry context, Field field, AttributeMetaData attributeInfo, Class targetClass) throws IllegalAccessException { + // Single valued - get the value of the field + Object fieldValue = field.get(entry); + // Ignore null field values + if (fieldValue != null) { + // Convert the field value to the required type and write it into the JNDI context + context.setAttributeValue(attributeInfo.getName().toString(), converterManager.convert(fieldValue, + attributeInfo.getSyntax(), targetClass)); + } + } + @Override public T mapFromLdapDataEntry(LdapDataEntry context, Class clazz) { if (LOG.isDebugEnabled()) { @@ -237,7 +253,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { NamingEnumeration attributesEnumeration = attributes.getAll(); // Loop through all of the JNDI attributes while (attributesEnumeration.hasMoreElements()) { - Attribute currentAttribute = (Attribute)attributesEnumeration.nextElement(); + Attribute currentAttribute = attributesEnumeration.nextElement(); // Add the current attribute to the map keyed on the lowercased (case indep) id of the attribute attributeValueMap.put(new CaseIgnoreString(currentAttribute.getID()), currentAttribute); } @@ -248,53 +264,27 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { // Get the current field AttributeMetaData attributeInfo = metaData.getAttribute(field); // We deal with the Id field specially - if (!attributeInfo.isId()) { + Name dn = context.getDn(); + if (!attributeInfo.isTransient() && !attributeInfo.isId()) { // Not the ID - but is is multi valued? if (!attributeInfo.isList()) { // No - its single valued, grab the JNDI attribute that corresponds to the metadata on the // current field - Attribute attribute = attributeValueMap.get(attributeInfo.getName()); - // There is no guarantee that this attribute is present in the directory - so ignore nulls - if (attribute != null) { - // Grab the JNDI value - Object value = attribute.get(); - // Check the value is not null - if (value != null) { - // Convert the JNDI value to its Java representation - this will throw if the - // conversion fails - Object convertedValue = converterManager.convert(value, attributeInfo.getSyntax(), - attributeInfo.getValueClass()); - // Set it in the Java version - field.set(result, convertedValue); - } - } - } else { // We are dealing with a multi valued attribute - // We need to build up a list of values - List fieldValues = new ArrayList(); - // 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 - if (currentAttribute != null) { - // Loop through the values of the JNDI attribute - NamingEnumeration valuesEmumeration = currentAttribute.getAll(); - while (valuesEmumeration.hasMore()) { - // Get the current value - Object value = valuesEmumeration.nextElement(); - // Check the value is not null - if (value != null) { - // Convert the value to its Java representation and add it to our working list - fieldValues.add(converterManager.convert(value, attributeInfo.getSyntax(), - attributeInfo.getValueClass())); - } - } - } - // Now we need to set the List in to a Java object - field.set(result, fieldValues); + populateSingleValueField(result, attributeValueMap, field, attributeInfo); + } else { + // We are dealing with a multi valued attribute + populateMultiValueField(result, attributeValueMap, field, attributeInfo); } - } else { // The id field - field.set(result, converterManager.convert(context.getDn(), attributeInfo.getSyntax(), + } else if(attributeInfo.isId()) { // The id field + field.set(result, converterManager.convert(dn, attributeInfo.getSyntax(), attributeInfo.getValueClass())); } + + DnAttribute dnAttribute = attributeInfo.getDnAttribute(); + if(dnAttribute != null) { + String dnValue = LdapUtils.getStringValue(dn, dnAttribute.value()); + field.set(result, dnValue); + } } // If this is the objectclass attribute then check that values correspond to the metadata we have @@ -315,7 +305,6 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { throw new InvalidEntryException(String.format("No object classes were returned for class %1$s", clazz.getName())); } - } catch (NamingException ne) { throw new InvalidEntryException(String.format("Problem creating %1$s from LDAP Entry %2$s", clazz, context), ne); @@ -333,6 +322,48 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { return result; } + 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(); + // 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 + if (currentAttribute != null) { + // Loop through the values of the JNDI attribute + NamingEnumeration valuesEmumeration = currentAttribute.getAll(); + while (valuesEmumeration.hasMore()) { + // Get the current value + Object value = valuesEmumeration.nextElement(); + // Check the value is not null + if (value != null) { + // Convert the value to its Java representation and add it to our working list + fieldValues.add(converterManager.convert(value, attributeInfo.getSyntax(), + attributeInfo.getValueClass())); + } + } + } + // Now we need to set the List in to a Java object + field.set(result, fieldValues); + } + + private void populateSingleValueField(T result, Map attributeValueMap, Field field, AttributeMetaData attributeInfo) throws NamingException, IllegalAccessException { + Attribute attribute = attributeValueMap.get(attributeInfo.getName()); + // There is no guarantee that this attribute is present in the directory - so ignore nulls + if (attribute != null) { + // Grab the JNDI value + Object value = attribute.get(); + // Check the value is not null + if (value != null) { + // Convert the JNDI value to its Java representation - this will throw if the + // conversion fails + Object convertedValue = converterManager.convert(value, attributeInfo.getSyntax(), + attributeInfo.getValueClass()); + // Set it in the Java version + field.set(result, convertedValue); + } + } + } + @Override public Name getId(Object entry) { try { @@ -343,6 +374,31 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { } } + @Override + public Name getCalculatedId(Object entry) { + Assert.notNull(entry, "Entry must not be null"); + EntityData entityData = getEntityData(entry.getClass()); + if(entityData.metaData.canCalculateDn()) { + Set dnAttributes = entityData.metaData.getDnAttributes(); + LdapNameBuilder ldapNameBuilder = LdapNameBuilder.newLdapName(entityData.metaData.getBase()); + + for (AttributeMetaData dnAttribute : dnAttributes) { + Object dnFieldValue = ReflectionUtils.getField(dnAttribute.getField(), entry); + if(dnFieldValue == null) { + throw new IllegalStateException( + String.format("DnAttribute for field %s on class %s is null; cannot build DN", + dnAttribute.getField().getName(), entry.getClass().getName())); + } + + ldapNameBuilder.add(dnAttribute.getDnAttribute().value(), dnFieldValue.toString()); + } + + return ldapNameBuilder.build(); + } + + return null; + } + @Override public Filter filterFor(Class clazz, Filter baseFilter) { Filter ocFilter = getEntityData(clazz).ocFilter; @@ -355,6 +411,11 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper { return andFilter.append(ocFilter).append(baseFilter); } + // For testing purposes + ConcurrentMap, EntityData> getMetaDataMap() { + return metaDataMap; + } + static boolean collectionContainsAll(Collection collection, Set shouldBePresent) { for (Object o : shouldBePresent) { if(!collection.contains(o)) { diff --git a/core/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java index 6c0f889d..5178c3e5 100755 --- a/core/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java +++ b/core/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java @@ -1,18 +1,22 @@ package org.springframework.ldap.odm.core.impl; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.ldap.odm.annotations.Entry; import org.springframework.ldap.odm.annotations.Id; -import org.springframework.ldap.odm.annotations.Transient; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.util.StringUtils; + +import javax.naming.Name; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; /* * An internal class to process the meta-data and reflection data for an entry. @@ -26,7 +30,23 @@ import org.springframework.ldap.odm.annotations.Transient; private Map fieldToAttribute = new HashMap(); - private Set objectClasses = new HashSet(); + private Set dnAttributes = new TreeSet(new Comparator() { + @Override + public int compare(AttributeMetaData a1, AttributeMetaData a2) { + if(!a1.isDnAttribute() || !a2.isDnAttribute()) { + // Not interesting to compare these. + return 0; + } + + return Integer.compare(a1.getDnAttribute().index(), a2.getDnAttribute().index()); + } + }); + + private boolean indexedDnAttributes = false; + + private Set objectClasses = new LinkedHashSet(); + + private Name base = LdapUtils.emptyLdapName(); public Set getObjectClasses() { return objectClasses; @@ -55,7 +75,7 @@ import org.springframework.ldap.odm.annotations.Transient; } // Get object class metadata - the @Entity annotation - Entry entity = (Entry)clazz.getAnnotation(Entry.class); + Entry entity = clazz.getAnnotation(Entry.class); if (entity != null) { // Default objectclass name to the class name unless it's specified // in @Entity(name={objectclass1, objectclass2}); @@ -67,6 +87,11 @@ import org.springframework.ldap.odm.annotations.Transient; } else { objectClasses.add(new CaseIgnoreString(clazz.getSimpleName())); } + + String base = entity.base(); + if(StringUtils.hasText(base)) { + this.base = LdapUtils.newLdapName(base); + } } else { throw new MetaDataException(String.format("Class %1$s must have a class level %2$s annotation", clazz, Entry.class)); @@ -83,11 +108,11 @@ import org.springframework.ldap.odm.annotations.Transient; // So we can write to private fields field.setAccessible(true); - // Skip transient and synthetic fields - if (field.getAnnotation(Transient.class) != null || field.isSynthetic()) { + // Skip synthetic fields + if (field.isSynthetic()) { continue; } - + AttributeMetaData currentAttributeMetaData=new AttributeMetaData(field); if (currentAttributeMetaData.isId()) { if (idAttribute!=null) { @@ -98,6 +123,10 @@ import org.springframework.ldap.odm.annotations.Transient; idAttribute=currentAttributeMetaData; } fieldToAttribute.put(field, currentAttributeMetaData); + + if(currentAttributeMetaData.isDnAttribute()) { + dnAttributes.add(currentAttributeMetaData); + } } if (idAttribute == null) { @@ -106,16 +135,58 @@ import org.springframework.ldap.odm.annotations.Transient; clazz)); } + postProcessDnAttributes(clazz); + if (LOG.isDebugEnabled()) { LOG.debug(String.format("Extracted metadata from %1$s as %2$s", clazz, this)); } } + private void postProcessDnAttributes(Class clazz) { + boolean hasIndexed = false; + boolean hasNonIndexed = false; + + for (AttributeMetaData dnAttribute : dnAttributes) { + int declaredIndex = dnAttribute.getDnAttribute().index(); + + if(declaredIndex != -1) { + hasIndexed = true; + } + + if(declaredIndex == -1) { + hasNonIndexed = true; + } + } + + if(hasIndexed && hasNonIndexed) { + throw new MetaDataException(String.format("At least one DnAttribute declared on class %s is indexed, " + + "which means that all DnAttributes must be indexed", clazz.toString())); + } + + indexedDnAttributes = hasIndexed; + } + + int size() { + return fieldToAttribute.size(); + } + + boolean canCalculateDn() { + return dnAttributes.size() > 0 && indexedDnAttributes; + } + + public Set getDnAttributes() { + return dnAttributes; + } + + Name getBase() { + return base; + } + /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ @Override public String toString() { return String.format("objectsClasses=%1$s | idField=%2$s | attributes=%3$s", diff --git a/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java b/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java index 9f4438bd..9d6a38eb 100644 --- a/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java +++ b/core/src/test/java/org/springframework/ldap/core/LdapTemplateLookupTest.java @@ -19,9 +19,11 @@ package org.springframework.ldap.core; import org.junit.Before; import org.junit.Test; import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; import org.springframework.ldap.support.LdapUtils; import javax.naming.Name; +import javax.naming.NamingException; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.ldap.LdapContext; @@ -49,6 +51,7 @@ public class LdapTemplateLookupTest { private ContextMapper contextMapperMock; private LdapTemplate tested; + private ObjectDirectoryMapper odmMock; @Before public void setUp() throws Exception { @@ -60,12 +63,12 @@ public class LdapTemplateLookupTest { // Setup Name mock nameMock = mock(Name.class); - contextMapperMock = mock(ContextMapper.class); - attributesMapperMock = mock(AttributesMapper.class); + odmMock = mock(ObjectDirectoryMapper.class); tested = new LdapTemplate(contextSourceMock); + tested.setObjectDirectoryMapper(odmMock); } private void expectGetReadOnlyContext() { @@ -192,6 +195,26 @@ public class LdapTemplateLookupTest { assertSame(transformed, actual); } + @Test + public void testFindByDn() throws NamingException { + expectGetReadOnlyContext(); + + Object transformed = new Object(); + Class expectedClass = Object.class; + + DirContextAdapter expectedContext = new DirContextAdapter(); + when(dirContextMock.lookup(nameMock)).thenReturn(expectedContext); + when(odmMock.mapFromLdapDataEntry(expectedContext, expectedClass)).thenReturn(transformed); + + // Perform test + Object result = tested.findByDn(nameMock, expectedClass); + assertSame(transformed, result); + + verify(odmMock).manageClass(expectedClass); + } + + + @Test public void testLookup_String_ContextMapper() throws Exception { expectGetReadOnlyContext(); diff --git a/core/src/test/java/org/springframework/ldap/core/LdapTemplateOdmTest.java b/core/src/test/java/org/springframework/ldap/core/LdapTemplateOdmTest.java new file mode 100644 index 00000000..22e4fe29 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/core/LdapTemplateOdmTest.java @@ -0,0 +1,34 @@ +package org.springframework.ldap.core; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; + +/** + * @author Mattias Hellborg Arthursson + */ +public class LdapTemplateOdmTest { + + private LdapTemplate tested; + private ObjectDirectoryMapper odmMock; + + @Before + public void prepareTestedClass() { + tested = mock(LdapTemplate.class); + + doCallRealMethod().when(tested).setObjectDirectoryMapper(any(ObjectDirectoryMapper.class)); + odmMock = mock(ObjectDirectoryMapper.class); + + tested.setObjectDirectoryMapper(odmMock); + } + + + @Test + public void testFindByDn() { + + } +} diff --git a/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java b/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java index aa47d996..d1d39be3 100644 --- a/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java +++ b/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java @@ -20,18 +20,23 @@ import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.LdapDataEntry; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.ldap.LimitExceededException; import org.springframework.ldap.NameNotFoundException; import org.springframework.ldap.PartialResultException; import org.springframework.ldap.UncategorizedLdapException; +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; import org.springframework.ldap.support.LdapUtils; import javax.naming.Binding; import javax.naming.CompositeName; import javax.naming.Name; import javax.naming.NamingEnumeration; +import javax.naming.NamingException; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; @@ -47,13 +52,17 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.ldap.query.LdapQueryBuilder.query; /** * Unit tests for the LdapTemplate class. @@ -92,42 +101,33 @@ public class LdapTemplateTest { private DirContext authenticatedContextMock; private AuthenticatedLdapEntryContextCallback entryContextCallbackMock; + private ObjectDirectoryMapper odmMock; @Before public void setUp() throws Exception { // Setup ContextSource mock contextSourceMock = mock(ContextSource.class); - // Setup LdapContext mock dirContextMock = mock(LdapContext.class); - // Setup NamingEnumeration mock namingEnumerationMock = mock(NamingEnumeration.class); - // Setup Name mock - nameMock = mock(Name.class); - + nameMock = LdapUtils.emptyLdapName(); // Setup Handler mock handlerMock = mock(NameClassPairCallbackHandler.class); - contextMapperMock = mock(ContextMapper.class); - attributesMapperMock = mock(AttributesMapper.class); - contextExecutorMock = mock(ContextExecutor.class); - searchExecutorMock = mock(SearchExecutor.class); - dirContextProcessorMock = mock(DirContextProcessor.class); - dirContextOperationsMock = mock(DirContextOperations.class); - authenticatedContextMock = mock(DirContext.class); - entryContextCallbackMock = mock(AuthenticatedLdapEntryContextCallback.class); + odmMock = mock(ObjectDirectoryMapper.class); - tested = new LdapTemplate(contextSourceMock); + tested = new LdapTemplate(contextSourceMock); + tested.setObjectDirectoryMapper(odmMock); } private void expectGetReadWriteContext() { @@ -591,7 +591,6 @@ public class LdapTemplateTest { Object expectedObject = new Object(); SearchResult searchResult = new SearchResult("", expectedObject, new BasicAttributes()); - singleSearchResult(searchControlsOneLevel(), searchResult); Object expectedResult = expectedObject; @@ -607,6 +606,79 @@ public class LdapTemplateTest { assertSame(expectedResult, list.get(0)); } + @Test + public void testFindOne() throws Exception { + Class expectedClass = Object.class; + + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock); + when(odmMock.filterFor(expectedClass, + new EqualsFilter("ou", "somevalue"))).thenReturn(new EqualsFilter("ou", "somevalue")); + + DirContextAdapter expectedObject = new DirContextAdapter(); + SearchResult searchResult = new SearchResult("", expectedObject, new BasicAttributes()); + singleSearchResult(searchControlsRecursive(), searchResult); + + Object expectedResult = expectedObject; + when(odmMock.mapFromLdapDataEntry(expectedObject, expectedClass)).thenReturn(expectedResult); + + Object result = tested.findOne(query() + .where("ou").is("somevalue"), expectedClass); + + verify(namingEnumerationMock).close(); + verify(dirContextMock).close(); + + assertSame(expectedResult, result); + } + + @Test + public void verifyThatFindOneThrowsEmptyResultIfNoResult() throws Exception { + Class expectedClass = Object.class; + + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock); + when(odmMock.filterFor(expectedClass, + new EqualsFilter("ou", "somevalue"))).thenReturn(new EqualsFilter("ou", "somevalue")); + + noSearchResults(searchControlsRecursive()); + + try { + tested.findOne(query().where("ou").is("somevalue"), expectedClass); + fail("EmptyResultDataAccessException expected"); + } catch (EmptyResultDataAccessException expected) { + assertTrue(true); + } + + verify(namingEnumerationMock).close(); + verify(dirContextMock).close(); + verify(odmMock, never()).mapFromLdapDataEntry(any(LdapDataEntry.class), any(Class.class)); + } + + @Test + public void verifyThatFindOneThrowsIncorrectResultSizeDataAccessExceptionWhenMoreResults() throws Exception { + Class expectedClass = Object.class; + + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock); + when(odmMock.filterFor(expectedClass, + new EqualsFilter("ou", "somevalue"))).thenReturn(new EqualsFilter("ou", "somevalue")); + + DirContextAdapter expectedObject = new DirContextAdapter(); + SearchResult searchResult = new SearchResult("", expectedObject, new BasicAttributes()); + + setupSearchResults(searchControlsRecursive(), new SearchResult[]{searchResult, searchResult}); + + Object expectedResult = expectedObject; + when(odmMock.mapFromLdapDataEntry(expectedObject, expectedClass)).thenReturn(expectedResult, expectedResult); + + try { + tested.findOne(query().where("ou").is("somevalue"), expectedClass); + fail("EmptyResultDataAccessException expected"); + } catch (IncorrectResultSizeDataAccessException expected) { + assertTrue(true); + } + + verify(namingEnumerationMock).close(); + verify(dirContextMock).close(); + } + @Test public void testSearch_ContextMapper_ReturningAttrs() throws Exception { expectGetReadOnlyContext(); @@ -963,6 +1035,128 @@ public class LdapTemplateTest { verify(dirContextMock).close(); } + @Test + public void testCreateWithIdSpecified() throws NamingException { + expectGetReadWriteContext(); + + Object expectedObject = new Object(); + LdapName expectedName = LdapUtils.newLdapName("ou=someOu"); + when(odmMock.getId(expectedObject)).thenReturn(expectedName); + + ArgumentCaptor ctxCaptor = ArgumentCaptor.forClass(DirContextAdapter.class); + doNothing().when(odmMock).mapToLdapDataEntry(eq(expectedObject), ctxCaptor.capture()); + + tested.create(expectedObject); + + verify(dirContextMock).bind(expectedName, ctxCaptor.getValue(), null); + verify(dirContextMock).close(); + } + + @Test + public void testCreateWithCalculatedId() throws NamingException { + expectGetReadWriteContext(); + + Object expectedObject = new Object(); + LdapName expectedName = LdapUtils.newLdapName("ou=someOu"); + when(odmMock.getId(expectedObject)).thenReturn(null); + when(odmMock.getCalculatedId(expectedObject)).thenReturn(expectedName); + + ArgumentCaptor ctxCaptor = ArgumentCaptor.forClass(DirContextAdapter.class); + doNothing().when(odmMock).mapToLdapDataEntry(eq(expectedObject), ctxCaptor.capture()); + + tested.create(expectedObject); + + verify(dirContextMock).bind(expectedName, ctxCaptor.getValue(), null); + verify(dirContextMock).close(); + } + + @Test + public void testCreateWithNoIdAvailableThrows() throws NamingException { + Object expectedObject = new Object(); + when(odmMock.getId(expectedObject)).thenReturn(null); + when(odmMock.getCalculatedId(expectedObject)).thenReturn(null); + + try { + tested.create(expectedObject); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + @Test + public void testUpdateWithIdSpecified() throws NamingException { + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock); + when(contextSourceMock.getReadWriteContext()).thenReturn(dirContextMock); + LdapName expectedName = LdapUtils.newLdapName("ou=someOu"); + + ModificationItem[] expectedModificationItems = new ModificationItem[0]; + DirContextOperations ctxMock = mock(DirContextOperations.class); + when(ctxMock.getDn()).thenReturn(expectedName); + when(ctxMock.isUpdateMode()).thenReturn(true); + when(ctxMock.getModificationItems()).thenReturn(expectedModificationItems); + + Object expectedObject = new Object(); + when(odmMock.getId(expectedObject)).thenReturn(expectedName); + when(odmMock.getCalculatedId(expectedObject)).thenReturn(null); + + when(dirContextMock.lookup(expectedName)).thenReturn(ctxMock); + + tested.update(expectedObject); + + verify(odmMock).mapToLdapDataEntry(expectedObject, ctxMock); + verify(dirContextMock).modifyAttributes(expectedName, expectedModificationItems); + + verify(dirContextMock, times(2)).close(); + } + + @Test + public void testUpdateWithIdCalculated() throws NamingException { + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock); + when(contextSourceMock.getReadWriteContext()).thenReturn(dirContextMock); + LdapName expectedName = LdapUtils.newLdapName("ou=someOu"); + + ModificationItem[] expectedModificationItems = new ModificationItem[0]; + DirContextOperations ctxMock = mock(DirContextOperations.class); + when(ctxMock.getDn()).thenReturn(expectedName); + when(ctxMock.isUpdateMode()).thenReturn(true); + when(ctxMock.getModificationItems()).thenReturn(expectedModificationItems); + + Object expectedObject = new Object(); + when(odmMock.getId(expectedObject)).thenReturn(null); + when(odmMock.getCalculatedId(expectedObject)).thenReturn(expectedName); + + when(dirContextMock.lookup(expectedName)).thenReturn(ctxMock); + + tested.update(expectedObject); + + verify(odmMock).mapToLdapDataEntry(expectedObject, ctxMock); + verify(dirContextMock).modifyAttributes(expectedName, expectedModificationItems); + + verify(dirContextMock, times(2)).close(); + } + + @Test + public void testUpdateWithIdChanged() throws NamingException { + Object expectedObject = new Object(); + + when(contextSourceMock.getReadWriteContext()).thenReturn(dirContextMock, dirContextMock); + LdapName expectedOriginalName = LdapUtils.newLdapName("ou=someOu"); + LdapName expectedNewName = LdapUtils.newLdapName("ou=someOtherOu"); + + ArgumentCaptor ctxCaptor = ArgumentCaptor.forClass(DirContextAdapter.class); + doNothing().when(odmMock).mapToLdapDataEntry(eq(expectedObject), ctxCaptor.capture()); + + when(odmMock.getId(expectedObject)).thenReturn(expectedOriginalName); + when(odmMock.getCalculatedId(expectedObject)).thenReturn(expectedNewName); + + tested.update(expectedObject); + + verify(dirContextMock).unbind(expectedOriginalName); + verify(dirContextMock).bind(expectedNewName, ctxCaptor.getValue(), null); + verify(dirContextMock, times(2)).close(); + } + @Test public void testUnbind() throws Exception { expectGetReadWriteContext(); 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 new file mode 100644 index 00000000..caff8a8b --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapperTest.java @@ -0,0 +1,122 @@ +package org.springframework.ldap.odm.core.impl; + +import org.junit.Before; +import org.junit.Test; +import org.junit.matchers.JUnitMatchers; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.util.StringUtils; + +import javax.naming.Name; +import java.lang.reflect.Field; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +/** + * @author Mattias Hellborg Arthursson + */ +public class DefaultObjectDirectoryMapperTest { + + private DefaultObjectDirectoryMapper tested; + + @Before + public void prepareTestedInstance() { + tested = new DefaultObjectDirectoryMapper(); + } + + @Test + public void testMapping() { + tested.manageClass(UnitTestPerson.class); + + DefaultObjectDirectoryMapper.EntityData entityData = tested.getMetaDataMap().get(UnitTestPerson.class); + + assertNotNull(entityData); + assertEquals(query(). + where("objectclass").is("inetOrgPerson") + .and("objectclass").is("organizationalPerson") + .and("objectclass").is("person") + .and("objectclass").is("top") + .filter(), entityData.ocFilter); + + assertEquals(7, entityData.metaData.size()); + + AttributeMetaData idAttribute = entityData.metaData.getIdAttribute(); + assertEquals("dn", idAttribute.getField().getName()); + assertTrue(idAttribute.isId()); + assertFalse(idAttribute.isBinary()); + assertFalse(idAttribute.isDnAttribute()); + assertFalse(idAttribute.isTransient()); + assertFalse(idAttribute.isList()); + + assertField(entityData, "fullName", "cn", "cn", false, false, false); + assertField(entityData, "lastName", "sn", null, false, false, false); + assertField(entityData, "description", "description", null, false, false, true); + assertField(entityData, "country", null, "c", false, true, false); + assertField(entityData, "company", null, "ou", false, true, false); + assertField(entityData, "telephoneNumber", "telephoneNumber", null, false, false, false); + } + + @Test + public void testInvalidType() { + try { + tested.manageClass(UnitTestPersonWithInvalidFieldType.class); + } catch (InvalidEntryException expected) { + assertThat(expected.getMessage(), JUnitMatchers.containsString("Missing converter from")); + } + } + + @Test + public void testIndexedDnAttributes() { + tested.manageClass(UnitTestPersonWithIndexedDnAttributes.class); + + UnitTestPersonWithIndexedDnAttributes testPerson = new UnitTestPersonWithIndexedDnAttributes(); + testPerson.setFullName("Some Person"); + testPerson.setCompany("Some Company"); + testPerson.setCountry("Sweden"); + + + Name calculatedId = tested.getCalculatedId(testPerson); + assertEquals(LdapUtils.newLdapName("cn=Some Person, ou=Some Company, c=Sweden"), calculatedId); + } + + @Test(expected = MetaDataException.class) + public void testIndexedDnAttributesRequiresThatAllAreIndexed() { + tested.manageClass(UnitTestPersonWithIndexedAndUnindexedDnAttributes.class); + } + + private void assertField(DefaultObjectDirectoryMapper.EntityData entityData, + String fieldName, + String expectedAttributeName, + String expectedDnAttributeName, + boolean expectedBinary, + boolean expectedTransient, + boolean expectedList) { + + for (Field field : entityData.metaData) { + if (fieldName.equals(field.getName())) { + AttributeMetaData attribute = entityData.metaData.getAttribute(field); + if (StringUtils.hasLength(expectedAttributeName)) { + assertEquals(expectedAttributeName, attribute.getName().toString()); + } else { + assertNull(attribute.getName()); + } + + if (StringUtils.hasLength(expectedDnAttributeName)) { + assertTrue(attribute.isDnAttribute()); + assertEquals(expectedDnAttributeName, attribute.getDnAttribute().value()); + } else { + assertFalse(attribute.isDnAttribute()); + } + + assertEquals(expectedBinary, attribute.isBinary()); + assertEquals(expectedTransient, attribute.isTransient()); + assertEquals(expectedList, attribute.isList()); + } + } + } +} diff --git a/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPerson.java b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPerson.java new file mode 100644 index 00000000..b2d8bf70 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPerson.java @@ -0,0 +1,57 @@ +/* + * 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.core.impl; + +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 org.springframework.ldap.odm.annotations.Transient; + +import javax.naming.Name; +import java.util.List; + +/** + * @author Mattias Hellborg Arthursson + */ +@Entry(objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"}) +public class UnitTestPerson { + @Id + private Name dn; + + @Attribute(name = "cn") + @DnAttribute("cn") + private String fullName; + + @Attribute(name = "sn") + private String lastName; + + @Attribute(name = "description") + private List description; + + @Transient + @DnAttribute("c") + private String country; + + @Transient + @DnAttribute("ou") + private String company; + + // This should be automatically found + private String telephoneNumber; + +} diff --git a/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedAndUnindexedDnAttributes.java b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedAndUnindexedDnAttributes.java new file mode 100644 index 00000000..eb1b8223 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedAndUnindexedDnAttributes.java @@ -0,0 +1,54 @@ +/* + * 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.core.impl; + +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; + +/** + * @author Mattias Hellborg Arthursson + */ +@Entry(objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"}) +public class UnitTestPersonWithIndexedAndUnindexedDnAttributes { + @Id + private Name dn; + + @DnAttribute(value = "cn", index=2) + private String fullName; + + // This makes the entry invalid + @DnAttribute(value = "ou") + private String company; + + @DnAttribute(value= "c", index=0) + private String country; + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public void setCompany(String company) { + this.company = company; + } + + public void setCountry(String country) { + this.country = country; + } +} diff --git a/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedDnAttributes.java b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedDnAttributes.java new file mode 100644 index 00000000..ca33b6ce --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithIndexedDnAttributes.java @@ -0,0 +1,53 @@ +/* + * 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.core.impl; + +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; + +/** + * @author Mattias Hellborg Arthursson + */ +@Entry(objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"}) +public class UnitTestPersonWithIndexedDnAttributes { + @Id + private Name dn; + + @DnAttribute(value = "cn", index=2) + private String fullName; + + @DnAttribute(value = "ou", index=1) + private String company; + + @DnAttribute(value= "c", index=0) + private String country; + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public void setCompany(String company) { + this.company = company; + } + + public void setCountry(String country) { + this.country = country; + } +} diff --git a/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithInvalidFieldType.java b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithInvalidFieldType.java new file mode 100644 index 00000000..6ffbaa95 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/odm/core/impl/UnitTestPersonWithInvalidFieldType.java @@ -0,0 +1,36 @@ +/* + * 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.core.impl; + +import org.springframework.ldap.odm.annotations.Attribute; +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.ldap.odm.annotations.Id; + +import javax.naming.Name; + +/** + * @author Mattias Hellborg Arthursson + */ +@Entry(objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"}) +public class UnitTestPersonWithInvalidFieldType { + @Id + private Name dn; + + @Attribute(name = "cn") + private Class fullName; + +} diff --git a/samples/odm/readme.txt b/samples/odm/readme.txt index 4d1191b5..18109c42 100644 --- a/samples/odm/readme.txt +++ b/samples/odm/readme.txt @@ -1,5 +1,5 @@ Sample application demonstrating how to do the most basic stuff in Spring LDAP using the Object-Directory Mapping facilities. -A very simple dao implementation is provided in org.springframework.ldap.samples.plain.dao.PersonDaoImpl +A very simple dao implementation is provided in org.springframework.ldap.samples.plain.dao.OdmPersonDaoImpl It demonstrates some basic operations using Spring LDAP Object-Directory Mapping. The core Spring application context of the sample is defined in resources/applicationContext.xml. diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java b/samples/odm/src/main/java/org/springframework/ldap/samples/odm/dao/OdmPersonDaoImpl.java similarity index 70% rename from samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java rename to samples/odm/src/main/java/org/springframework/ldap/samples/odm/dao/OdmPersonDaoImpl.java index c4560155..eeacdc50 100644 --- a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/odm/dao/OdmPersonDaoImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2010 the original author or authors. + * 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. @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.ldap.samples.plain.dao; +package org.springframework.ldap.samples.odm.dao; import org.springframework.ldap.core.AttributesMapper; import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.samples.plain.dao.PersonDao; import org.springframework.ldap.samples.plain.domain.Person; import org.springframework.ldap.support.LdapNameBuilder; @@ -28,33 +29,23 @@ import java.util.List; import static org.springframework.ldap.query.LdapQueryBuilder.query; /** - * Default implementation of PersonDao. This implementation uses - * DirContextAdapter for managing attribute values. We use a ContextMapper - * to map from the found contexts to our domain objects. This is especially useful - * since we in this case have properties in our domain objects that depend on parts of the DN. - * - * We could have worked with Attributes and an AttributesMapper implementation - * instead, but working with Attributes is a bore and also, working with - * AttributesMapper objects (or, indeed Attributes) does not give us access to - * the distinguished name. However, we do use it in one method that only needs a - * single attribute: {@link #getAllPersonNames()}. + * Default implementation of PersonDao. This implementation uses the Object-Directory Mapping feature, + * which requires the entity classes to be annotated, but relieves the programmer from the tedious + * task of mapping to and from entity objects, using attribute or dn component values. * * @author Mattias Hellborg Arthursson - * @author Ulrik Sandberg */ -public class PersonDaoImpl implements PersonDao { +public class OdmPersonDaoImpl implements PersonDao { private LdapTemplate ldapTemplate; @Override public void create(Person person) { - person.setDn(buildDn(person)); ldapTemplate.create(person); } @Override public void update(Person person) { - person.setDn(buildDn(person)); ldapTemplate.update(person); } @@ -85,11 +76,6 @@ public class PersonDaoImpl implements PersonDao { LdapName dn = buildDn(country, company, fullname); Person person = ldapTemplate.findByDn(dn, Person.class); - // TODO: This needs to happen automatically - person.setCountry(country); - person.setCompany(company); - person.setFullName(fullname); - return person; } diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java index c3043903..9fa605f9 100644 --- a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2010 the original author or authors. + * 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. @@ -20,6 +20,7 @@ import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; 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 org.springframework.ldap.odm.annotations.Transient; @@ -38,6 +39,7 @@ public class Person { private Name dn; @Attribute(name = "cn") + @DnAttribute(value = "cn", index = 2) private String fullName; @Attribute(name = "sn") @@ -47,9 +49,11 @@ public class Person { private String description; @Transient + @DnAttribute(value = "c", index = 0) private String country; @Transient + @DnAttribute(value = "ou", index = 1) private String company; @Attribute(name = "telephoneNumber") diff --git a/samples/odm/src/main/resources/applicationContext.xml b/samples/odm/src/main/resources/applicationContext.xml index 55561d29..b39e1648 100644 --- a/samples/odm/src/main/resources/applicationContext.xml +++ b/samples/odm/src/main/resources/applicationContext.xml @@ -45,7 +45,7 @@ + class="org.springframework.ldap.samples.odm.dao.OdmPersonDaoImpl"> diff --git a/samples/odm/src/test/java/org/springframework/ldap/samples/plain/dao/PersonDaoSampleIntegrationTest.java b/samples/odm/src/test/java/org/springframework/ldap/samples/odm/dao/PersonDaoSampleIntegrationTest.java similarity index 93% rename from samples/odm/src/test/java/org/springframework/ldap/samples/plain/dao/PersonDaoSampleIntegrationTest.java rename to samples/odm/src/test/java/org/springframework/ldap/samples/odm/dao/PersonDaoSampleIntegrationTest.java index fd2bcb43..bd45edb8 100644 --- a/samples/odm/src/test/java/org/springframework/ldap/samples/plain/dao/PersonDaoSampleIntegrationTest.java +++ b/samples/odm/src/test/java/org/springframework/ldap/samples/odm/dao/PersonDaoSampleIntegrationTest.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.ldap.samples.plain.dao; +package org.springframework.ldap.samples.odm.dao; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.samples.plain.dao.PersonDao; import org.springframework.ldap.samples.plain.domain.Person; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; @@ -81,7 +82,10 @@ public class PersonDaoSampleIntegrationTest extends assertEquals( "Another description", result .getDescription()); - } finally { + } catch(Exception e){ + e.printStackTrace(); + } + finally { personDao.delete(person); try { personDao.findByPrimaryKey( diff --git a/samples/odm/src/test/resources/config/testContext.xml b/samples/odm/src/test/resources/config/testContext.xml index 7b38dc02..10a688cb 100644 --- a/samples/odm/src/test/resources/config/testContext.xml +++ b/samples/odm/src/test/resources/config/testContext.xml @@ -23,7 +23,7 @@ + class="org.springframework.ldap.samples.odm.dao.OdmPersonDaoImpl"> diff --git a/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java b/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java index 1814d19c..8206619f 100644 --- a/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java +++ b/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2010 the original author or authors. + * 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. diff --git a/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/PersonWithDnAnnotations.java b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/PersonWithDnAnnotations.java new file mode 100644 index 00000000..d1369a6a --- /dev/null +++ b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/PersonWithDnAnnotations.java @@ -0,0 +1,107 @@ +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 org.springframework.ldap.odm.annotations.Transient; + +import javax.naming.Name; +import java.util.List; + +/** + * @author Mattias Hellborg Arthursson + */ +@Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }) +public class PersonWithDnAnnotations { + @Id + private Name dn; + + @Attribute(name = "cn") + @DnAttribute(value="cn", index=2) + private String commonName; + + @Attribute(name = "sn") + private String surname; + + @Attribute(name = "description") + private List desc; + + @Attribute(name = "uid") + private List userId; + + @Attribute(name = "telephoneNumber") + private String telephoneNumber; + + @DnAttribute(value="ou", index=1) + @Transient + private String company; + + @DnAttribute(value="c", index=0) + @Transient + private String country; + + public Name getDn() { + return dn; + } + + public void setDn(Name dn) { + this.dn = dn; + } + + public String getCommonName() { + return commonName; + } + + public void setCommonName(String commonName) { + this.commonName = commonName; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public List getDesc() { + return desc; + } + + public void setDesc(List desc) { + this.desc = desc; + } + + public List getUserId() { + return userId; + } + + public void setUserId(List userId) { + this.userId = userId; + } + + public String getTelephoneNumber() { + return telephoneNumber; + } + + public void setTelephoneNumber(String telephoneNumber) { + this.telephoneNumber = telephoneNumber; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } +} diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmWithDnAnnotationsITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmWithDnAnnotationsITest.java new file mode 100644 index 00000000..1602f421 --- /dev/null +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmWithDnAnnotationsITest.java @@ -0,0 +1,161 @@ +/* + * 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 java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +/** + * @author Mattias Hellborg Arthursson + */ +@ContextConfiguration(locations = {"/conf/ldapTemplateTestContext.xml"}) +public class LdapTemplateOdmWithDnAnnotationsITest extends AbstractLdapTemplateIntegrationTest { + @Autowired + private LdapTemplate tested; + + @Test + public void testFindOne() { + PersonWithDnAnnotations person = tested.findOne(query() + .where("cn").is("Some Person3"), PersonWithDnAnnotations.class); + + assertNotNull(person); + assertEquals("Some Person3", person.getCommonName()); + assertEquals("Person3", person.getSurname()); + assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); + assertEquals("+46 555-123654", person.getTelephoneNumber()); + + // Automatically calculated + assertEquals("company1", person.getCompany()); + assertEquals("Sweden", person.getCountry()); + } + + @Test + public void testFindByDn() { + PersonWithDnAnnotations person = tested.findByDn(LdapUtils.newLdapName("cn=Some Person3,ou=company1,c=Sweden"), + PersonWithDnAnnotations.class); + + assertNotNull(person); + assertEquals("Some Person3", person.getCommonName()); + assertEquals("Person3", person.getSurname()); + assertEquals("Sweden, Company1, Some Person3", person.getDesc().get(0)); + assertEquals("+46 555-123654", person.getTelephoneNumber()); + + // Automatically calculated + assertEquals("company1", person.getCompany()); + assertEquals("Sweden", person.getCountry()); + } + + @Test + public void testFindInCountry() { + List persons = tested.find(query() + .base("c=Sweden") + .where("cn").isPresent(), PersonWithDnAnnotations.class); + + assertEquals(4, persons.size()); + + PersonWithDnAnnotations person = findPerson(persons, "Some Person3"); + + // Automatically calculated + assertEquals("company1", person.getCompany()); + assertEquals("Sweden", person.getCountry()); + } + + private PersonWithDnAnnotations findPerson(List persons, String cn) { + for (PersonWithDnAnnotations person : persons) { + if(person.getCommonName().equals(cn)) { + return person; + } + } + + fail(String.format("Person with cn %s not found", cn)); + // we'll never get here + return null; + } + + @Test + public void testCreateWithCalculatedDn() { + PersonWithDnAnnotations person = new PersonWithDnAnnotations(); + + // Don't explicitly set DN. + person.setCommonName("New Person"); + person.setSurname("Person"); + person.setDesc(Arrays.asList("This is the description")); + person.setTelephoneNumber("0123456"); + person.setCompany("company1"); + person.setCountry("Sweden"); + + tested.create(person); + + assertEquals(6, tested.findAll(PersonWithDnAnnotations.class).size()); + + person = tested.findByDn(LdapUtils.newLdapName("cn=New Person,ou=company1,c=Sweden"), + PersonWithDnAnnotations.class); + + assertEquals("New Person", person.getCommonName()); + assertEquals("Person", person.getSurname()); + assertEquals("This is the description", person.getDesc().get(0)); + assertEquals("0123456", person.getTelephoneNumber()); + + // Automatically calculated + assertEquals("company1", person.getCompany()); + assertEquals("Sweden", person.getCountry()); + } + +// @Test +// public void testUpdate() { +// Person person = tested.findOne(query() +// .where("cn").is("Some Person3"), Person.class); +// +// person.setDesc(Arrays.asList("New Description")); +// tested.update(person); +// +// person = tested.findOne(query() +// .where("cn").is("Some Person3"), Person.class); +// +// assertEquals("Some Person3", person.getCommonName()); +// assertEquals("Person3", person.getSurname()); +// assertEquals("New Description", person.getDesc().get(0)); +// assertEquals("+46 555-123654", person.getTelephoneNumber()); +// } +// +// @Test +// public void testDelete() { +// Person person = tested.findOne(query() +// .where("cn").is("Some Person3"), Person.class); +// +// tested.delete(person); +// +// try { +// tested.findOne(query().where("cn").is("Some Person3"), Person.class); +// fail("EmptyResultDataAccessException e"); +// } catch (EmptyResultDataAccessException e) { +// assertTrue(true); +// } +// } +} diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmWithNoDnAnnotationsITest.java similarity index 98% rename from test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmITest.java rename to test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmWithNoDnAnnotationsITest.java index c8283ef7..3f4969dc 100644 --- a/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmITest.java +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmWithNoDnAnnotationsITest.java @@ -39,7 +39,7 @@ import static org.springframework.ldap.query.LdapQueryBuilder.query; * @author Mattias Hellborg Arthursson */ @ContextConfiguration(locations = {"/conf/ldapTemplateTestContext.xml"}) -public class LdapTemplateOdmITest extends AbstractLdapTemplateIntegrationTest { +public class LdapTemplateOdmWithNoDnAnnotationsITest extends AbstractLdapTemplateIntegrationTest { @Autowired private LdapTemplate tested;