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.

This commit is contained in:
Mattias Hellborg Arthursson
2013-09-29 16:11:14 +02:00
parent 1b814e3631
commit 78b9617bf1
25 changed files with 1212 additions and 149 deletions

View File

@@ -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<T>() {
@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

View File

@@ -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;
}

View File

@@ -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 "";
}

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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<String> attributeValues = new ArrayList<String>();
// 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<String> attributeValues = new ArrayList<String>();
// 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> T mapFromLdapDataEntry(LdapDataEntry context, Class<T> clazz) {
if (LOG.isDebugEnabled()) {
@@ -237,7 +253,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
NamingEnumeration<? extends Attribute> 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<Object> fieldValues = new ArrayList<Object>();
// 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 <T> void populateMultiValueField(T result, Map<CaseIgnoreString, Attribute> attributeValueMap, Field field, AttributeMetaData attributeInfo) throws NamingException, IllegalAccessException {
// We need to build up a list of values
List<Object> fieldValues = new ArrayList<Object>();
// 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 <T> void populateSingleValueField(T result, Map<CaseIgnoreString, Attribute> 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<AttributeMetaData> 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<Class<?>, EntityData> getMetaDataMap() {
return metaDataMap;
}
static boolean collectionContainsAll(Collection<?> collection, Set<?> shouldBePresent) {
for (Object o : shouldBePresent) {
if(!collection.contains(o)) {

View File

@@ -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<Field, AttributeMetaData> fieldToAttribute = new HashMap<Field, AttributeMetaData>();
private Set<CaseIgnoreString> objectClasses = new HashSet<CaseIgnoreString>();
private Set<AttributeMetaData> dnAttributes = new TreeSet<AttributeMetaData>(new Comparator<AttributeMetaData>() {
@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<CaseIgnoreString> objectClasses = new LinkedHashSet<CaseIgnoreString>();
private Name base = LdapUtils.emptyLdapName();
public Set<CaseIgnoreString> 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<AttributeMetaData> 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",

View File

@@ -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<Object> 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();

View File

@@ -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() {
}
}

View File

@@ -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<Object> 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<Object> 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<Object> 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<DirContextAdapter> 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<DirContextAdapter> 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<DirContextAdapter> 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();

View File

@@ -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());
}
}
}
}

View File

@@ -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<String> description;
@Transient
@DnAttribute("c")
private String country;
@Transient
@DnAttribute("ou")
private String company;
// This should be automatically found
private String telephoneNumber;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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")

View File

@@ -45,7 +45,7 @@
</bean>
<bean id="personDao"
class="org.springframework.ldap.samples.plain.dao.PersonDaoImpl">
class="org.springframework.ldap.samples.odm.dao.OdmPersonDaoImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>

View File

@@ -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(

View File

@@ -23,7 +23,7 @@
</bean>
<bean id="personDao"
class="org.springframework.ldap.samples.plain.dao.PersonDaoImpl">
class="org.springframework.ldap.samples.odm.dao.OdmPersonDaoImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>

View File

@@ -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.

View File

@@ -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<String> desc;
@Attribute(name = "uid")
private List<String> 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<String> getDesc() {
return desc;
}
public void setDesc(List<String> desc) {
this.desc = desc;
}
public List<String> getUserId() {
return userId;
}
public void setUserId(List<String> 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;
}
}

View File

@@ -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<PersonWithDnAnnotations> 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<PersonWithDnAnnotations> 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);
// }
// }
}

View File

@@ -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;