LDAP-265: Moved ODM functionality to core and added methods in LdapTemplate.

This commit is contained in:
Mattias Hellborg Arthursson
2013-09-27 10:43:07 +02:00
parent 5b388cb5c6
commit 1b814e3631
67 changed files with 2374 additions and 728 deletions

View File

@@ -0,0 +1,178 @@
package org.springframework;
import javax.naming.Name;
import javax.naming.directory.Attributes;
import java.util.SortedSet;
/**
* Common data access methods for entries in an LDAP tree.
*
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
public interface LdapDataEntry {
/**
* Get the value of a String attribute. If more than one attribute value
* exists for the specified attribute, only the first one will be returned.
* If an attribute has no value, <code>null</code> will be returned.
*
* @param name name of the attribute.
* @return the value of the attribute if it exists, or <code>null</code> if
* the attribute doesn't exist or if it exists but with no value.
* @throws ClassCastException if the value of the entry is not a String.
*/
String getStringAttribute(String name);
/**
* Get the value of an Object attribute. If more than one attribute value
* exists for the specified attribute, only the first one will be returned.
* If an attribute has no value, <code>null</code> will be returned.
*
* @param name name of the attribute.
* @return the attribute value as an object if it exists, or
* <code>null</code> if the attribute doesn't exist or if it exists but with
* no value.
*/
Object getObjectAttribute(String name);
/**
* Check if an Object attribute exists, regardless of whether it has a value
* or not.
*
* @param name name of the attribute
* @return <code>true</code> if the attribute exists, <code>false</code>
* otherwise
*/
boolean attributeExists(String name);
/**
* Set the with the name <code>name</code> to the <code>value</code>.
*
* @param name name of the attribute.
* @param value value to set the attribute to.
*/
public void setAttributeValue(String name, Object value);
/**
* Sets a multivalue attribute, disregarding the order of the values.
*
* If value is null or value.length == 0 then the attribute will be removed.
*
* If update mode, changes will be made only if the array has more or less
* objects or if one or more object has changed. Reordering the objects will
* not cause an update.
*
* @param name The id of the attribute.
* @param values Attribute values.
*/
void setAttributeValues(String name, Object[] values);
/**
* Sets a multivalue attribute.
*
* If value is null or value.length == 0 then the attribute will be removed.
*
* If update mode, changes will be made if the array has more or less
* objects or if one or more string has changed.
*
* Reordering the objects will only cause an update if orderMatters is set
* to true.
*
* @param name The id of the attribute.
* @param values Attribute values.
* @param orderMatters If <code>true</code>, it will be changed even if data
* was just reordered.
*/
void setAttributeValues(String name, Object[] values, boolean orderMatters);
/**
* Add a value to the Attribute with the specified name. If the Attribute
* doesn't exist it will be created. This method makes sure that the there
* will be no duplicates of an added value - it the value exists it will not
* be added again.
*
* @param name the name of the Attribute to which the specified value should
* be added.
* @param value the Attribute value to add.
*/
void addAttributeValue(String name, Object value);
/**
* Add a value to the Attribute with the specified name. If the Attribute
* doesn't exist it will be created. The <code>addIfDuplicateExists</code>
* parameter controls the handling of duplicates. It <code>false</code>,
* this method makes sure that the there will be no duplicates of an added
* value - it the value exists it will not be added again.
*
* @param name the name of the Attribute to which the specified value should
* be added.
* @param value the Attribute value to add.
* @param addIfDuplicateExists <code>true</code> will add the value
* regardless of whether there is an identical value already, allowing for
* duplicate attribute values; <code>false</code> will not add the value if
* it already exists.
*/
void addAttributeValue(String name, Object value,
boolean addIfDuplicateExists);
/**
* Remove a value from the Attribute with the specified name. If the
* Attribute doesn't exist, do nothing.
*
* @param name the name of the Attribute from which the specified value
* should be removed.
* @param value the value to remove.
*/
void removeAttributeValue(String name, Object value);
/**
* Get all values of a String attribute.
*
* @param name name of the attribute.
* @return a (possibly empty) array containing all registered values of the
* attribute as Strings if the attribute is defined or <code>null</code>
* otherwise.
* @throws IllegalArgumentException if any of the attribute values is not a
* String.
*/
String[] getStringAttributes(String name);
/**
* Get all values of an Object attribute.
*
* @param name name of the attribute.
* @return a (possibly empty) array containing all registered values of the
* attribute if the attribute is defined or <code>null</code> otherwise.
* @since 1.3
*/
Object[] getObjectAttributes(String name);
/**
* Get all String values of the attribute as a <code>SortedSet</code>.
*
* @param name name of the attribute.
* @return a <code>SortedSet</code> containing all values of the attribute,
* or <code>null</code> if the attribute does not exist.
* @throws IllegalArgumentException if one of the found attribute values cannot be cast to a String.
*/
SortedSet<String> getAttributeSortedStringSet(String name);
/**
* Returns the DN relative to the base path.
* <b>NB</b>: as of version 2.0 the returned name will be an LdapName instance.
*
* @return The distinguished name of the current context.
*
* @see org.springframework.ldap.core.DirContextAdapter#getNameInNamespace()
*/
Name getDn();
/**
* Get all the Attributes.
*
* @return all the Attributes.
* @since 1.3
*/
Attributes getAttributes();
}

View File

@@ -16,10 +16,10 @@
package org.springframework.ldap.core;
import org.springframework.LdapDataEntry;
import javax.naming.Name;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import java.util.SortedSet;
/**
* Interface for DirContextAdapter.
@@ -27,7 +27,7 @@ import java.util.SortedSet;
* @author Mattias Hellborg Arthursson
* @see DirContextAdapter
*/
public interface DirContextOperations extends DirContext,
public interface DirContextOperations extends DirContext, LdapDataEntry,
AttributeModificationsAware {
/**
@@ -52,120 +52,6 @@ public interface DirContextOperations extends DirContext,
*/
String[] getNamesOfModifiedAttributes();
/**
* Get the value of a String attribute. If more than one attribute value
* exists for the specified attribute, only the first one will be returned.
* If an attribute has no value, <code>null</code> will be returned.
*
* @param name name of the attribute.
* @return the value of the attribute if it exists, or <code>null</code> if
* the attribute doesn't exist or if it exists but with no value.
* @throws ClassCastException if the value of the entry is not a String.
*/
String getStringAttribute(String name);
/**
* Get the value of an Object attribute. If more than one attribute value
* exists for the specified attribute, only the first one will be returned.
* If an attribute has no value, <code>null</code> will be returned.
*
* @param name name of the attribute.
* @return the attribute value as an object if it exists, or
* <code>null</code> if the attribute doesn't exist or if it exists but with
* no value.
*/
Object getObjectAttribute(String name);
/**
* Check if an Object attribute exists, regardless of whether it has a value
* or not.
*
* @param name name of the attribute
* @return <code>true</code> if the attribute exists, <code>false</code>
* otherwise
*/
boolean attributeExists(String name);
/**
* Set the with the name <code>name</code> to the <code>value</code>.
*
* @param name name of the attribute.
* @param value value to set the attribute to.
*/
public void setAttributeValue(String name, Object value);
/**
* Sets a multivalue attribute, disregarding the order of the values.
*
* If value is null or value.length == 0 then the attribute will be removed.
*
* If update mode, changes will be made only if the array has more or less
* objects or if one or more object has changed. Reordering the objects will
* not cause an update.
*
* @param name The id of the attribute.
* @param values Attribute values.
*/
void setAttributeValues(String name, Object[] values);
/**
* Sets a multivalue attribute.
*
* If value is null or value.length == 0 then the attribute will be removed.
*
* If update mode, changes will be made if the array has more or less
* objects or if one or more string has changed.
*
* Reordering the objects will only cause an update if orderMatters is set
* to true.
*
* @param name The id of the attribute.
* @param values Attribute values.
* @param orderMatters If <code>true</code>, it will be changed even if data
* was just reordered.
*/
void setAttributeValues(String name, Object[] values, boolean orderMatters);
/**
* Add a value to the Attribute with the specified name. If the Attribute
* doesn't exist it will be created. This method makes sure that the there
* will be no duplicates of an added value - it the value exists it will not
* be added again.
*
* @param name the name of the Attribute to which the specified value should
* be added.
* @param value the Attribute value to add.
*/
void addAttributeValue(String name, Object value);
/**
* Add a value to the Attribute with the specified name. If the Attribute
* doesn't exist it will be created. The <code>addIfDuplicateExists</code>
* parameter controls the handling of duplicates. It <code>false</code>,
* this method makes sure that the there will be no duplicates of an added
* value - it the value exists it will not be added again.
*
* @param name the name of the Attribute to which the specified value should
* be added.
* @param value the Attribute value to add.
* @param addIfDuplicateExists <code>true</code> will add the value
* regardless of whether there is an identical value already, allowing for
* duplicate attribute values; <code>false</code> will not add the value if
* it already exists.
*/
void addAttributeValue(String name, Object value,
boolean addIfDuplicateExists);
/**
* Remove a value from the Attribute with the specified name. If the
* Attribute doesn't exist, do nothing.
*
* @param name the name of the Attribute from which the specified value
* should be removed.
* @param value the value to remove.
*/
void removeAttributeValue(String name, Object value);
/**
* Update the attributes.This will mean that the getters (
* <code>getStringAttribute</code> methods) will return the updated values,
@@ -175,48 +61,6 @@ public interface DirContextOperations extends DirContext,
*/
void update();
/**
* Get all values of a String attribute.
*
* @param name name of the attribute.
* @return a (possibly empty) array containing all registered values of the
* attribute as Strings if the attribute is defined or <code>null</code>
* otherwise.
* @throws IllegalArgumentException if any of the attribute values is not a
* String.
*/
String[] getStringAttributes(String name);
/**
* Get all values of an Object attribute.
*
* @param name name of the attribute.
* @return a (possibly empty) array containing all registered values of the
* attribute if the attribute is defined or <code>null</code> otherwise.
* @since 1.3
*/
Object[] getObjectAttributes(String name);
/**
* Get all String values of the attribute as a <code>SortedSet</code>.
*
* @param name name of the attribute.
* @return a <code>SortedSet</code> containing all values of the attribute,
* or <code>null</code> if the attribute does not exist.
* @throws IllegalArgumentException if one of the found attribute values cannot be cast to a String.
*/
SortedSet<String> getAttributeSortedStringSet(String name);
/**
* Returns the DN relative to the base path.
* <b>NB</b>: as of version 2.0 the returned name will be an LdapName instance.
*
* @return The distinguished name of the current context.
*
* @see DirContextAdapter#getNameInNamespace()
*/
Name getDn();
/**
* Set the dn of this entry.
*
@@ -250,12 +94,4 @@ public interface DirContextOperations extends DirContext,
* @since 1.3
*/
boolean isReferral();
/**
* Get all the Attributes.
*
* @return all the Attributes.
* @since 1.3
*/
Attributes getAttributes();
}

View File

@@ -20,6 +20,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.ContextNotEmptyException;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.support.AbstractContextSource;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.support.LdapUtils;
@@ -1676,4 +1677,120 @@ public interface LdapOperations {
* @see org.springframework.ldap.query.LdapQueryBuilder
*/
<T> T searchForObject(LdapQuery query, ContextMapper<T> mapper);
/**
* Read a named entry from the LDAP directory.
*
* @param <T> The Java type to return
* @param dn The distinguished name of the entry to read from the LDAP directory.
* @param clazz The Java type to return
* @return The entry as read from the directory
*
* @throws org.springframework.ldap.NamingException on error.
* @since 2.0
*/
<T> T findByDn(Name dn, Class<T> clazz);
/**
* Create the given entry in the LDAP directory.
*
* @param entry The entry to be create, it must <em>not</em> already exist in the directory.
*
* @throws org.springframework.ldap.NamingException on error.
* @since 2.0
*/
void create(Object entry);
/**
* Update the given entry in the LDAP directory.
*
* @param entry The entry to update, it must already exist in the directory.
*
* @throws org.springframework.ldap.NamingException on error.
* @since 2.0
*/
void update(Object entry);
/**
* Delete an entry from the LDAP directory.
*
* @param entry The entry to delete, it must already exist in the directory.
*
* @throws org.springframework.ldap.NamingException on error.
* @since 2.0
*/
void delete(Object entry);
/**
* Find all entries in the LDAP directory of a given type.
*
* @param <T> The Java type to return
* @param clazz The Java type to return
* @return All entries that are of the type represented by the given
* Java class
*
* @throws org.springframework.ldap.NamingException on error.
* @since 2.0
*/
<T> List<T> findAll(Class<T> clazz);
/**
* Find all entries in the LDAP directory of a given type.
*
* @param <T> The Java type to return
* @param base The root of the sub-tree at which to begin the search.
* @param searchControls The search controls of the search. Note that the 'returned attributes' parameter should
* typically not be tampered with, since that may affect the attributes populated in returned entries.
* @param clazz The Java type to return
* @return All entries that are of the type represented by the given
* Java class
*
* @throws org.springframework.ldap.NamingException on error.
* @since 2.0
*/
<T> List<T> findAll(Name base, SearchControls searchControls, Class<T> clazz);
/**
* Find all entries in the LDAP directory of a given type that matches the specified filter.
*
* @param <T> The Java type to return
* @param base The root of the sub-tree at which to begin the search.
* @param filter The search filter.
* @param searchControls The search controls of the search. Note that the 'returned attributes' parameter should
* typically not be tampered with, since that may affect the attributes populated in returned entries.
* @param clazz The Java type to return
* @return All entries that are of the type represented by the given
* Java class
*
* @throws org.springframework.ldap.NamingException on error.
* @since 2.0
*/
public <T> List<T> find(Name base, Filter filter, SearchControls searchControls, Class<T> clazz);
/**
* Search for entries in the LDAP directory.
* <p>
* Only those entries that both match the query search filter and
* are represented by the given Java class are returned.
*
* @param <T> The Java type to return
* @param query the LDAP query specification
* @param clazz The Java type to return
* @return All matching entries.
*
* @throws org.springframework.ldap.NamingException on error.
* @see org.springframework.ldap.query.LdapQueryBuilder
* @since 2.0
*/
<T> List<T> find(LdapQuery query, Class<T> clazz);
/**
*
* @param query
* @param clazz
* @param <T>
* @return
* @since 2.0
*/
<T> T findOne(LdapQuery query, Class<T> clazz);
}

View File

@@ -23,6 +23,10 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
import org.springframework.ldap.odm.core.OdmException;
import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.Assert;
@@ -83,6 +87,8 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
private int defaultCountLimit = 0;
private ObjectDirectoryMapper odm = new DefaultObjectDirectoryMapper();
/**
* Constructor for bean usage.
*/
@@ -108,7 +114,17 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
this.contextSource = contextSource;
}
/**
/**
* Set the ObjectDirectoryMapper instance to use.
*
* @param odm the ObejctDirectoryMapper to use.
* @since 2.0
*/
public void setObjectDirectoryMapper(ObjectDirectoryMapper odm) {
this.odm = odm;
}
/**
* Get the ContextSource.
*
* @return the ContextSource.
@@ -1713,4 +1729,124 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
searchControls,
mapper);
}
@Override
public <T> T findByDn(Name dn, final Class<T> clazz) {
if (log.isDebugEnabled()) {
log.debug(String.format("Reading Entry at - %s$1", dn));
}
// TODO: validate class before lookup
// getEntityData(clazz);
T result = lookup(dn, new ContextMapper<T>() {
@Override
public T mapFromContext(Object ctx) throws javax.naming.NamingException {
return odm.mapFromLdapDataEntry((DirContextOperations) ctx, clazz);
}
});
if (result == null) {
throw new OdmException(String.format("Entry %1$s does not have the required objectclasses ", dn));
}
if (log.isDebugEnabled()) {
log.debug(String.format("Found entry - %s$1", result));
}
return result;
}
@Override
public void create(Object entry) {
if (log.isDebugEnabled()) {
log.debug(String.format("Creating entry - %s$1", entry));
}
DirContextAdapter context = new DirContextAdapter(odm.getId(entry));
odm.mapToLdapDataEntry(entry, context);
bind(context);
}
@Override
public void update(Object entry) {
if (log.isDebugEnabled()) {
log.debug(String.format("Updating entry - %s$1", entry));
}
DirContextOperations context = lookupContext(odm.getId(entry));
odm.mapToLdapDataEntry(entry, context);
modifyAttributes(context);
}
@Override
public void delete(Object entry) {
if (log.isDebugEnabled()) {
log.debug(String.format("Deleting %s$1", entry));
}
// Just to check that this is a managed class
unbind(odm.getId(entry));
}
@Override
public <T> List<T> findAll(Name base, SearchControls searchControls, final Class<T> clazz) {
return find(base, null, searchControls, clazz);
}
@Override
public <T> List<T> findAll(Class<T> clazz) {
return findAll(LdapUtils.emptyLdapName(),
getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, ALL_ATTRIBUTES),
clazz);
}
@Override
public <T> List<T> find(Name base, Filter filter, SearchControls searchControls, final Class<T> clazz) {
Filter finalFilter = odm.filterFor(clazz, filter);
// Search from the root if we are not told where to search from
Name localBase = base;
if (base == null || base.size() == 0) {
localBase = LdapUtils.emptyLdapName();
}
if (log.isDebugEnabled()) {
log.debug(String.format("Searching - base=%1$s, finalFilter=%2$s, scope=%3$s", base, finalFilter, searchControls));
}
List<T> result = search(localBase, finalFilter.encode(), searchControls, new ContextMapper<T>() {
@Override
public T mapFromContext(Object ctx) throws javax.naming.NamingException {
return odm.mapFromLdapDataEntry((DirContextOperations) ctx, clazz);
}
});
result.remove(null);
if (log.isDebugEnabled()) {
log.debug(String.format("Found %1$s Entries - %2$s", result.size(), result));
}
return result;
}
@Override
public <T> List<T> find(LdapQuery query, Class<T> clazz) {
SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG);
return find(query.base(), query.filter(), searchControls, clazz);
}
@Override
public <T> T findOne(LdapQuery query, Class<T> clazz) {
List<T> result = find(query, clazz);
if (result.size() == 0) {
throw new EmptyResultDataAccessException(1);
}
else if (result.size() != 1) {
throw new IncorrectResultSizeDataAccessException(1, result.size());
}
return result.get(0);
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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;
import org.springframework.LdapDataEntry;
import org.springframework.ldap.filter.Filter;
import javax.naming.Name;
/**
* The ObjectDirectoryMapper keeps track of managed class metadata and is used by {@link org.springframework.ldap.core.LdapTemplate}
* to map to/from entity objects annotated with the annotations specified in the {@link org.springframework.ldap.odm.annotations}
* package. Instances of this class are typically intended for internal use only.
*
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
public interface ObjectDirectoryMapper {
/**
* Used to convert from Java representation of an Ldap Entry when writing to
* the Ldap directory
*
* @param entry - The entry to convert.
* @param context - The LDAP context to store the converted entry
* @throws org.springframework.ldap.NamingException on error.
*/
void mapToLdapDataEntry(Object entry, LdapDataEntry context);
/**
* Used to convert from the JNDI LDAP representation of an Entry to the Java representation when reading from LDAP.
* @throws org.springframework.ldap.NamingException on error.
*/
<T> T mapFromLdapDataEntry(LdapDataEntry ctx, Class<T> clazz);
/**
* Get the distinguished name for the specified object.
*
* @param entry the entry to get distinguished name for.
* @return the distinguished name of the entry.
* @throws org.springframework.ldap.NamingException on error.
*/
Name getId(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.
*
* @param clazz the class.
* @param baseFilter the filter we want to use.
* @return the original filter, modified so that it only applies to entries of the specified class.
* @throws org.springframework.ldap.NamingException on error.
*/
Filter filterFor(Class<?> clazz, Filter baseFilter);
/**
* Check if the specified class is already managed by this instance; if not, check the metadata and add the class to the
* managed classes.
*
* @param clazz the class to manage.
* @throws org.springframework.ldap.NamingException on error.
*/
void manageClass(Class<?> clazz);
}

View File

@@ -0,0 +1,367 @@
/*
* 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.core.ObjectDirectoryMapper;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
import org.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Default implementation of {@link ObjectDirectoryMapper}. Unless you need to explicitly configure
* converters there is typically no reason to explicitly consider yourself with this class.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
* @author Mattias Hellborg Arthursson
* @since 2.0
*/
public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
private static final Log LOG = LogFactory.getLog(DefaultObjectDirectoryMapper.class);
// The converter manager to use to translate values between LDAP and Java
private ConverterManager converterManager;
private static String OBJECT_CLASS_ATTRIBUTE="objectclass";
private static CaseIgnoreString OBJECT_CLASS_ATTRIBUTE_CI=new CaseIgnoreString(OBJECT_CLASS_ATTRIBUTE);
public DefaultObjectDirectoryMapper() {
this.converterManager = new ConverterManagerImpl();
}
public void setConverterManager(ConverterManager converterManager) {
this.converterManager = converterManager;
}
private static final class EntityData {
private final ObjectMetaData metaData;
private final Filter ocFilter;
private EntityData(ObjectMetaData metaData, Filter ocFilter) {
this.metaData=metaData;
this.ocFilter=ocFilter;
}
}
// A map of managed classes to to meta data about those classes
private final ConcurrentMap<Class<?>, EntityData> metaDataMap=new ConcurrentHashMap<Class<?>, EntityData>();
private EntityData getEntityData(Class<?> managedClass) {
EntityData result = metaDataMap.get(managedClass);
if (result == null) {
return addManagedClass(managedClass);
}
return result;
}
@Override
public void manageClass(Class<?> clazz) {
// This throws exception if data is invalid
getEntityData(clazz);
}
/**
* Adds an {@link org.springframework.ldap.odm.annotations} annotated class to the set
* managed by this OdmManager.
*
* @param managedClass The class to add to the managed set.
*/
private EntityData addManagedClass(Class<?> managedClass) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Adding class %1$s to managed set", managedClass));
}
// Extract the meta-data from the class
ObjectMetaData metaData=new ObjectMetaData(managedClass);
// Check we can construct the target type - it must have a zero argument public constructor
try {
managedClass.getConstructor();
} catch (NoSuchMethodException e) {
throw new InvalidEntryException(String.format(
"The class %1$s must have a zero argument constructor to be an Entry", managedClass));
}
// 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())) {
Class<?> jndiClass = (attributeInfo.isBinary()) ? byte[].class : String.class;
Class<?> javaClass = attributeInfo.getValueClass();
if (!converterManager.canConvert(jndiClass, attributeInfo.getSyntax(), javaClass)) {
throw new InvalidEntryException(String.format(
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
jndiClass, javaClass, field.getName(), managedClass));
}
if (!converterManager.canConvert(javaClass, attributeInfo.getSyntax(), jndiClass)) {
throw new InvalidEntryException(String.format(
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
javaClass, jndiClass, field.getName(), managedClass));
}
}
}
// Filter so we only read the object classes supported by the managedClass
AndFilter ocFilter = new AndFilter();
for (CaseIgnoreString oc : metaData.getObjectClasses()) {
ocFilter.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, oc.toString()));
}
EntityData newValue = new EntityData(metaData, ocFilter);
EntityData previousValue = metaDataMap.putIfAbsent(managedClass, newValue);
// Just in case someone beat us to it
if(previousValue != null) {
return previousValue;
}
return newValue;
}
@Override
public void mapToLdapDataEntry(Object entry, LdapDataEntry context) {
ObjectMetaData metaData=getEntityData(entry.getClass()).metaData;
Attribute objectclassAttribute = context.getAttributes().get(OBJECT_CLASS_ATTRIBUTE);
if(objectclassAttribute == null || objectclassAttribute.size() == 0) {
// Object classes are set from the metadata obtained from the @Entity annotation,
// but only if this is a new entry.
int numOcs=metaData.getObjectClasses().size();
CaseIgnoreString[] metaDataObjectClasses=metaData.getObjectClasses().toArray(new CaseIgnoreString[numOcs]);
String[] stringOcs=new String[numOcs];
for (int ocIndex=0; ocIndex<numOcs; ocIndex++) {
stringOcs[ocIndex]=metaDataObjectClasses[ocIndex].toString();
}
context.setAttributeValues(OBJECT_CLASS_ATTRIBUTE, stringOcs);
}
// Loop through each of the fields in the object to write to LDAP
for (Field field : metaData) {
// 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())) {
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());
}
}
} catch (IllegalAccessException e) {
throw new InvalidEntryException(String.format("Can't set attribute %1$s", attributeInfo.getName()),
e);
}
}
}
}
@Override
public <T> T mapFromLdapDataEntry(LdapDataEntry context, Class<T> clazz) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Converting to Java Entry class %1$s from %2$s", clazz, context));
}
// The Java representation of the LDAP entry
T result;
ObjectMetaData metaData=getEntityData(clazz).metaData;
try {
// The result class must have a zero argument constructor
result = clazz.newInstance();
// Build a map of JNDI attribute names to values
Map<CaseIgnoreString, Attribute> attributeValueMap = new HashMap<CaseIgnoreString, Attribute>();
// Get a NamingEnumeration to loop through the JNDI attributes in the entry
Attributes attributes = context.getAttributes();
NamingEnumeration<? extends Attribute> attributesEnumeration = attributes.getAll();
// Loop through all of the JNDI attributes
while (attributesEnumeration.hasMoreElements()) {
Attribute currentAttribute = (Attribute)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);
}
// Now loop through all the fields in the Java representation populating it with values from the
// attributeValueMap
for (Field field : metaData) {
// Get the current field
AttributeMetaData attributeInfo = metaData.getAttribute(field);
// We deal with the Id field specially
if (!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);
}
} else { // The id field
field.set(result, converterManager.convert(context.getDn(), attributeInfo.getSyntax(),
attributeInfo.getValueClass()));
}
}
// If this is the objectclass attribute then check that values correspond to the metadata we have
// for the Java representation
Attribute ocAttribute = attributeValueMap.get(OBJECT_CLASS_ATTRIBUTE_CI);
if (ocAttribute != null) {
// Get all object class values from the JNDI attribute
Set<CaseIgnoreString> objectClassesFromJndi = new HashSet<CaseIgnoreString>();
NamingEnumeration<?> objectClassesFromJndiEnum = ocAttribute.getAll();
while (objectClassesFromJndiEnum.hasMoreElements()) {
objectClassesFromJndi.add(new CaseIgnoreString((String)objectClassesFromJndiEnum.nextElement()));
}
// OK - checks its the same as the meta-data we have
if(!collectionContainsAll(objectClassesFromJndi, metaData.getObjectClasses())) {
return null;
}
} else {
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);
} catch (IllegalAccessException iae) {
throw new InvalidEntryException(String.format(
"Could not create an instance of %1$s could not access field", clazz.getName()), iae);
} catch (InstantiationException ie) {
throw new InvalidEntryException(String.format("Could not instantiate %1$s", clazz), ie);
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Converted object - %1$s", result));
}
return result;
}
@Override
public Name getId(Object entry) {
try {
return (Name)getEntityData(entry.getClass()).metaData.getIdAttribute().getField().get(entry);
} catch (Exception e) {
throw new InvalidEntryException(String.format("Can't get Id field from Entry %1$s", entry),
e);
}
}
@Override
public Filter filterFor(Class<?> clazz, Filter baseFilter) {
Filter ocFilter = getEntityData(clazz).ocFilter;
if(baseFilter == null) {
return ocFilter;
}
AndFilter andFilter = new AndFilter();
return andFilter.append(ocFilter).append(baseFilter);
}
static boolean collectionContainsAll(Collection<?> collection, Set<?> shouldBePresent) {
for (Object o : shouldBePresent) {
if(!collection.contains(o)) {
return false;
}
}
return true;
}
}

View File

@@ -1,11 +1,11 @@
package org.springframework.ldap.odm.typeconversion.impl;
import java.util.HashMap;
import java.util.Map;
import org.springframework.ldap.odm.typeconversion.ConverterException;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
import java.util.HashMap;
import java.util.Map;
/**
* An implementation of {@link org.springframework.ldap.odm.typeconversion.ConverterManager}.
* <p>

View File

@@ -52,7 +52,7 @@ public class LdapNameBuilder {
*
* @return a new instance.
*/
public static LdapNameBuilder newInstance(Name name) {
public static LdapNameBuilder newLdapName(Name name) {
return new LdapNameBuilder(LdapUtils.newLdapName(name));
}
@@ -62,7 +62,7 @@ public class LdapNameBuilder {
*
* @return a new instance.
*/
public static LdapNameBuilder newInstance(String name) {
public static LdapNameBuilder newLdapName(String name) {
return new LdapNameBuilder(LdapUtils.newLdapName(name));
}

View File

@@ -36,6 +36,7 @@ import java.util.List;
* @see org.springframework.ldap.odm.annotations.Attribute
* @see org.springframework.ldap.odm.annotations.Id
* @see org.springframework.ldap.odm.annotations.Transient
* @deprecated This functionality is automatically available in LdapTemplate as of version 2.0
*/
public interface OdmManager {

View File

@@ -16,36 +16,19 @@
package org.springframework.ldap.odm.core.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.simple.ParameterizedContextMapper;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.odm.core.OdmException;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.filter.HardcodedFilter;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.SearchScope;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.StringUtils;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -55,43 +38,31 @@ import java.util.Set;
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
* @author Mattias Hellborg Arthursson
*
* @deprecated This functionality is automatically available in LdapTemplate as of version 2.0
*/
public final class OdmManagerImpl implements OdmManager {
private static final Log LOG = LogFactory.getLog(OdmManagerImpl.class);
// The link to the LDAP directory
private final LdapOperations ldapTemplate;
private final LdapTemplate ldapTemplate;
// The converter manager to use to translate values between LDAP and Java
private final ConverterManager converterManager;
private static String OBJECT_CLASS_ATTRIBUTE="objectclass";
private static CaseIgnoreString OBJECT_CLASS_ATTRIBUTE_CI=new CaseIgnoreString(OBJECT_CLASS_ATTRIBUTE);
private static final class EntityData {
private final ObjectMetaData metaData;
private final String ocFilter;
private EntityData(ObjectMetaData metaData, String ocFilter) {
this.metaData=metaData;
this.ocFilter=ocFilter;
}
}
// A map of managed classes to to meta data about those classes
private final Map<Class<?>, EntityData> metaDataMap=new HashMap<Class<?>, EntityData>();
private DefaultObjectDirectoryMapper objectDirectoryMapper;
public OdmManagerImpl(ConverterManager converterManager,
LdapOperations ldapOperations,
Set<Class<?>> managedClasses) {
this.converterManager=converterManager;
this.ldapTemplate = ldapOperations;
this.ldapTemplate = (LdapTemplate)ldapOperations;
objectDirectoryMapper = new DefaultObjectDirectoryMapper();
if(converterManager != null) {
objectDirectoryMapper.setConverterManager(converterManager);
}
if (managedClasses!=null) {
for (Class<?> managedClass: managedClasses) {
addManagedClass(managedClass);
}
}
this.ldapTemplate.setObjectDirectoryMapper(objectDirectoryMapper);
}
public OdmManagerImpl(ConverterManager converterManager,
@@ -105,14 +76,6 @@ public final class OdmManagerImpl implements OdmManager {
this(converterManager, contextSource, null);
}
private EntityData getEntityData(Class<?> managedClass) {
EntityData result=metaDataMap.get(managedClass);
if (result==null) {
throw new UnmanagedClassException(String.format("The %1$s class is not managed by this OdmManager", managedClass));
}
return result;
}
/**
* Adds an {@link org.springframework.ldap.odm.annotations} annotated class to the set
* managed by this OdmManager.
@@ -120,47 +83,7 @@ public final class OdmManagerImpl implements OdmManager {
* @param managedClass The class to add to the managed set.
*/
public void addManagedClass(Class<?> managedClass) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Adding class %1$s to managed set", managedClass));
}
// Extract the meta-data from the class
ObjectMetaData metaData=new ObjectMetaData(managedClass);
// Check we can construct the target type - it must have a zero argument public constructor
try {
managedClass.getConstructor();
} catch (NoSuchMethodException e) {
throw new InvalidEntryException(String.format(
"The class %1$s must have a zero argument constructor to be an Entry", managedClass));
}
// 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())) {
Class<?> jndiClass = (attributeInfo.isBinary()) ? byte[].class : String.class;
Class<?> javaClass = attributeInfo.getValueClass();
if (!converterManager.canConvert(jndiClass, attributeInfo.getSyntax(), javaClass)) {
throw new InvalidEntryException(String.format(
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
jndiClass, javaClass, field.getName(), managedClass));
}
if (!converterManager.canConvert(javaClass, attributeInfo.getSyntax(), jndiClass)) {
throw new InvalidEntryException(String.format(
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
javaClass, jndiClass, field.getName(), managedClass));
}
}
}
// Filter so we only read the object classes supported by the managedClass
AndFilter ocFilter = new AndFilter();
for (CaseIgnoreString oc : metaData.getObjectClasses()) {
ocFilter.and(new EqualsFilter(OBJECT_CLASS_ATTRIBUTE, oc.toString()));
}
metaDataMap.put(managedClass, new EntityData(metaData, ocFilter.encode()));
objectDirectoryMapper.manageClass(managedClass);
}
/*
@@ -169,21 +92,7 @@ public final class OdmManagerImpl implements OdmManager {
* @see org.springframework.ldap.odm.core.OdmManager#create(java.lang.Object)
*/
public <T> T read(Class<T> clazz, Name dn) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Reading Entry at - %s$1", dn));
}
getEntityData(clazz);
T result = clazz.cast(ldapTemplate.lookup(dn, new GenericContextMapper<T>(clazz)));
if (result==null) {
throw new OdmException(String.format("Entry %1$s does not have the required objectclasses ", dn));
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Found entry - %s$1", result));
}
return result;
return ldapTemplate.findByDn(dn, clazz);
}
/*
@@ -192,13 +101,7 @@ public final class OdmManagerImpl implements OdmManager {
* @see org.springframework.ldap.odm.core.OdmManager#create(java.lang.Object)
*/
public void create(Object entry) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Creating entry - %s$1", entry));
}
DirContextAdapter context = new DirContextAdapter(getId(entry));
mapToContext(entry, context);
ldapTemplate.bind(context);
ldapTemplate.create(entry);
}
/*
@@ -207,13 +110,7 @@ public final class OdmManagerImpl implements OdmManager {
* @see org.springframework.ldap.odm.core.OdmManager#update(java.lang.Object, boolean)
*/
public void update(Object entry) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Updating entry - %s$1", entry));
}
DirContextOperations context = ldapTemplate.lookupContext(getId(entry));
mapToContext(entry, context);
ldapTemplate.modifyAttributes(context);
ldapTemplate.update(entry);
}
/*
@@ -222,309 +119,27 @@ public final class OdmManagerImpl implements OdmManager {
* @see org.springframework.ldap.odm.core.OdmManager#delete(javax.naming.Name)
*/
public void delete(Object entry) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Deleting %s$1", entry));
}
// Just to check that this is a managed class
getEntityData(entry.getClass());
ldapTemplate.unbind(getId(entry));
ldapTemplate.delete(entry);
}
private Name getId(Object entry) {
try {
return (Name)getEntityData(entry.getClass()).metaData.getIdAttribute().getField().get(entry);
} catch (Exception e) {
throw new InvalidEntryException(String.format("Can't get Id field from Entry %1$s", entry),
e);
}
}
/* (non-Javadoc)
* @see org.springframework.ldap.odm.core.OdmManager#search(java.lang.Class, javax.naming.Name, java.lang.String, javax.naming.directory.SearchControls)
*/
public <T> List<T> search(Class<T> managedClass, Name base, String filter, SearchControls scope) {
EntityData entityData=getEntityData(managedClass);
// Add a filter so we only read the object class we can deal with
String finalFilter = entityData.ocFilter;
if (filter != null && filter.length() != 0) {
StringBuilder fixedFilter = new StringBuilder();
fixedFilter.append("(&(").append(filter).append(")").append(entityData.ocFilter).append(")");
finalFilter = fixedFilter.toString();
Filter searchFilter = null;
if(StringUtils.hasText(filter)) {
searchFilter = new HardcodedFilter(filter);
}
// Search from the root if we are not told where to search from
Name localBase = base;
if (base == null || base.size() == 0) {
localBase = LdapUtils.emptyLdapName();
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Searching - base=%1$s, finalFilter=%2$s, scope=%3$s", base, finalFilter, scope));
}
@SuppressWarnings("unchecked")
List<T> result = ldapTemplate.search(localBase, finalFilter, scope, new GenericContextMapper<T>(managedClass));
result.remove(null);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Found %1$s Entries - %2$s", result.size(), result));
}
return result;
return ldapTemplate.find(base, searchFilter, scope, managedClass);
}
@Override
public <T> List<T> search(Class<T> clazz, LdapQuery query) {
SearchControls searchControls = new SearchControls();
SearchScope searchScope = query.searchScope();
if(searchScope == null) {
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
} else {
searchControls.setSearchScope(searchScope.getId());
}
Integer countLimit = query.countLimit();
if(countLimit != null) {
searchControls.setCountLimit(countLimit);
}
Integer timeLimit = query.timeLimit();
if(timeLimit != null) {
searchControls.setCountLimit(timeLimit);
}
// Defaults to null which means 'all', so if it's not set we're still good.
searchControls.setReturningAttributes(query.attributes());
return search(clazz, query.base(), query.filter().encode(), searchControls);
return ldapTemplate.find(query, clazz);
}
public <T> List<T> findAll(Class<T> managedClass, Name base, SearchControls scope) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Searching for all Entries with objectClass=%1$s, with base=%2$s, scope=%3$s",
getEntityData(managedClass).metaData.getObjectClasses(), base, scope));
}
return search(managedClass, base, null, scope);
return ldapTemplate.findAll(base, scope, managedClass);
}
/**
* Used to convert from Java representation of an Ldap Entry when writing to
* the Ldap directory
*
* @param entry - The entry to convert.
* @param context - The LDAP context to store the converted entry
* @throws javax.naming.NamingEnumeration on error.
*/
private void mapToContext(Object entry, DirContextOperations context) {
ObjectMetaData metaData=getEntityData(entry.getClass()).metaData;
Attribute objectclassAttribute = context.getAttributes().get(OBJECT_CLASS_ATTRIBUTE);
if(objectclassAttribute == null || objectclassAttribute.size() == 0) {
// Object classes are set from the metadata obtained from the @Entity annotation,
// but only if this is a new entry.
int numOcs=metaData.getObjectClasses().size();
CaseIgnoreString[] metaDataObjectClasses=metaData.getObjectClasses().toArray(new CaseIgnoreString[numOcs]);
String[] stringOcs=new String[numOcs];
for (int ocIndex=0; ocIndex<numOcs; ocIndex++) {
stringOcs[ocIndex]=metaDataObjectClasses[ocIndex].toString();
}
context.setAttributeValues(OBJECT_CLASS_ATTRIBUTE, stringOcs);
}
// Loop through each of the fields in the object to write to LDAP
for (Field field : metaData) {
// 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())) {
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());
}
}
} catch (IllegalAccessException e) {
throw new InvalidEntryException(String.format("Can't set attribute %1$s", attributeInfo.getName()),
e);
}
}
}
}
/**
* Used to convert from the JNDI LDAP representation of an Entry to the Java representation when reading from LDAP
*/
private class GenericContextMapper<T> implements ParameterizedContextMapper<T> {
private final Class<T> managedClass;
private GenericContextMapper(Class<T> managedClass) {
this.managedClass=managedClass;
}
// Called by Spring LDAP to do the conversion
/*
* (non-Javadoc)
*
* @see org.springframework.ldap.core.simple.ParameterizedContextMapper#mapFromContext(java.lang.Object)
*/
public T mapFromContext(Object object) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Converting to Java Entry class %1$s from %2$s", managedClass, object));
}
// The Java representation of the LDAP entry
T result = null;
// This is guaranteed by Spring LDAP to be a DirContextOperations
DirContextOperations context = (DirContextOperations)object;
ObjectMetaData metaData=getEntityData(managedClass).metaData;
try {
// The result class must have a zero argument constructor
result = managedClass.newInstance();
// Build a map of JNDI attribute names to values
Map<CaseIgnoreString, Attribute> attributeValueMap = new HashMap<CaseIgnoreString, Attribute>();
// Get a NamingEnumeration to loop through the JNDI attributes in the entry
Attributes attributes = context.getAttributes();
NamingEnumeration<? extends Attribute> attributesEnumeration = attributes.getAll();
// Loop through all of the JNDI attributes
while (attributesEnumeration.hasMoreElements()) {
Attribute currentAttribute = (Attribute)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);
}
// Now loop through all the fields in the Java representation populating it with values from the
// attributeValueMap
for (Field field : metaData) {
// Get the current field
AttributeMetaData attributeInfo = metaData.getAttribute(field);
// We deal with the Id field specially
if (!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);
}
} else { // The id field
field.set(result, converterManager.convert(context.getDn(), attributeInfo.getSyntax(),
attributeInfo.getValueClass()));
}
}
// If this is the objectclass attribute then check that values correspond to the metadata we have
// for the Java representation
Attribute ocAttribute = attributeValueMap.get(OBJECT_CLASS_ATTRIBUTE_CI);
if (ocAttribute != null) {
// Get all object class values from the JNDI attribute
Set<CaseIgnoreString> objectClassesFromJndi = new HashSet<CaseIgnoreString>();
NamingEnumeration<?> objectClassesFromJndiEnum = ocAttribute.getAll();
while (objectClassesFromJndiEnum.hasMoreElements()) {
objectClassesFromJndi.add(new CaseIgnoreString((String)objectClassesFromJndiEnum.nextElement()));
}
// OK - checks its the same as the meta-data we have
if(!collectionContainsAll(objectClassesFromJndi, metaData.getObjectClasses())) {
return null;
}
} else {
throw new InvalidEntryException(String.format("No object classes were returned for class %1$s",
managedClass.getName()));
}
} catch (NamingException ne) {
throw new InvalidEntryException(String.format("Problem creating %1$s from LDAP Entry %2$s",
managedClass, object), ne);
} catch (IllegalAccessException iae) {
throw new InvalidEntryException(String.format(
"Could not create an instance of %1$s could not access field", managedClass.getName()), iae);
} catch (InstantiationException ie) {
throw new InvalidEntryException(String.format("Could not instantiate %1$s", managedClass), ie);
}
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Converted object - %1$s", result));
}
return result;
}
}
static boolean collectionContainsAll(Collection<?> collection, Set<?> shouldBePresent) {
for (Object o : shouldBePresent) {
if(!collection.contains(o)) {
return false;
}
}
return true;
}
}

View File

@@ -43,6 +43,7 @@ import java.util.Set;
* </pre>
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
* @deprecated This functionality is automatically available in LdapTemplate as of version 2.0
*/
public final class OdmManagerImplFactoryBean implements FactoryBean {
private LdapOperations ldapOperations = null;

View File

@@ -42,7 +42,6 @@ import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.ldap.odm.core.impl.InvalidEntryException;
import org.springframework.ldap.odm.core.impl.MetaDataException;
import org.springframework.ldap.odm.core.impl.OdmManagerImpl;
import org.springframework.ldap.odm.core.impl.UnmanagedClassException;
import org.springframework.ldap.odm.test.utils.ExecuteRunnable;
import org.springframework.ldap.odm.test.utils.GetFreePort;
import org.springframework.ldap.odm.test.utils.RunnableTest;
@@ -577,7 +576,7 @@ public final class TestLdap {
}
// The OdmManager should flag any attempt to use a "unmanaged" class
@Test(expected = UnmanagedClassException.class)
@Test(expected = MetaDataException.class)
public void unManagedClass() {
odmManager.read(Integer.class, baseName);
}

16
samples/odm/build.gradle Normal file
View File

@@ -0,0 +1,16 @@
apply from: JAVA_SCRIPT
apply plugin: 'war'
apply plugin: 'jetty'
dependencies {
compile project(':spring-ldap-test'),
'log4j:log4j:1.2.9',
'javax.servlet:jstl:1.2',
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-webmvc:$springVersion"
provided "javax.servlet:servlet-api:2.5"
testCompile "org.springframework:spring-test:$springVersion",
"junit:junit:$junitVersion"
}

9
samples/odm/readme.txt Normal file
View File

@@ -0,0 +1,9 @@
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
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.
This ApplicationContext will start an in-process Apache Directory Server instance, automatically populated
with some test data. The data will be reset every time the application is restarted.
To run the example, do 'gradle jettyRun', and then navigate to http://localhost:8080/spring-ldap-plain-sample

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2005-2010 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.samples.plain.dao;
import org.springframework.ldap.samples.plain.domain.Person;
import java.util.List;
/**
* Data Access Object interface for the Person entity.
*
* @author Mattias Hellborg Arthursson
* @author Ulrik Sandberg
*/
public interface PersonDao {
void create(Person person);
void update(Person person);
void delete(Person person);
List<String> getAllPersonNames();
List<Person> findAll();
Person findByPrimaryKey(String country, String company, String fullname);
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2005-2010 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.samples.plain.dao;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.samples.plain.domain.Person;
import org.springframework.ldap.support.LdapNameBuilder;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
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()}.
*
* @author Mattias Hellborg Arthursson
* @author Ulrik Sandberg
*/
public class PersonDaoImpl 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);
}
@Override
public void delete(Person person) {
ldapTemplate.delete(ldapTemplate.findByDn(buildDn(person), Person.class));
}
@Override
public List<String> getAllPersonNames() {
return ldapTemplate.search(query()
.attributes("cn")
.where("objectclass").is("person"),
new AttributesMapper<String>() {
public String mapFromAttributes(Attributes attrs) throws NamingException {
return attrs.get("cn").get().toString();
}
});
}
@Override
public List<Person> findAll() {
return ldapTemplate.findAll(Person.class);
}
@Override
public Person findByPrimaryKey(String country, String company, String fullname) {
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;
}
private LdapName buildDn(Person person) {
return buildDn(person.getCountry(), person.getCompany(), person.getFullName());
}
private LdapName buildDn(String country, String company, String fullname) {
return LdapNameBuilder.newInstance()
.add("c", country)
.add("ou", company)
.add("cn", fullname)
.build();
}
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2005-2010 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.samples.plain.domain;
import org.apache.commons.lang.builder.EqualsBuilder;
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.Entry;
import org.springframework.ldap.odm.annotations.Id;
import org.springframework.ldap.odm.annotations.Transient;
import javax.naming.Name;
/**
* Simple class representing a single person.
*
* @author Mattias Hellborg Arthursson
* @author Ulrik Sandberg
*/
@Entry(objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"})
public class Person {
@Id
private Name dn;
@Attribute(name = "cn")
private String fullName;
@Attribute(name = "sn")
private String lastName;
@Attribute(name = "description")
private String description;
@Transient
private String country;
@Transient
private String company;
@Attribute(name = "telephoneNumber")
private String phone;
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn = dn;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
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;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(
this, obj);
}
public int hashCode() {
return HashCodeBuilder
.reflectionHashCode(this);
}
public String toString() {
return ToStringBuilder.reflectionToString(
this, ToStringStyle.MULTI_LINE_STYLE);
}
}

View File

@@ -0,0 +1,130 @@
package org.springframework.ldap.samples.plain.web;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.samples.plain.dao.PersonDao;
import org.springframework.ldap.samples.plain.domain.Person;
import org.springframework.ldap.samples.utils.HtmlRowLdapTreeVisitor;
import org.springframework.ldap.samples.utils.LdapTree;
import org.springframework.ldap.samples.utils.LdapTreeBuilder;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.naming.Name;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* Default controller.
*
* @author Mattias Hellborg Arthursson
*/
@Controller
public class DefaultController {
@Autowired
private LdapTreeBuilder ldapTreeBuilder;
@Autowired
private PersonDao personDao;
@RequestMapping("/welcome.do")
public void welcomeHandler() {
}
@RequestMapping("/showTree.do")
public ModelAndView showTree() {
LdapTree ldapTree = ldapTreeBuilder.getLdapTree(LdapUtils.emptyLdapName());
HtmlRowLdapTreeVisitor visitor = new PersonLinkHtmlRowLdapTreeVisitor();
ldapTree.traverse(visitor);
return new ModelAndView("showTree", "rows", visitor.getRows());
}
@RequestMapping("/addPerson.do")
public String addPerson() {
Person person = getPerson();
personDao.create(person);
return "redirect:/showTree.do";
}
@RequestMapping("/updatePhoneNumber.do")
public String updatePhoneNumber() {
Person person = personDao.findByPrimaryKey("Sweden", "company1", "John Doe");
person.setPhone(StringUtils.join(new String[] { person.getPhone(), "0" }));
personDao.update(person);
return "redirect:/showTree.do";
}
@RequestMapping("/removePerson.do")
public String removePerson() {
Person person = getPerson();
personDao.delete(person);
return "redirect:/showTree.do";
}
@RequestMapping("/showPerson.do")
public ModelMap showPerson(String country, String company, String fullName) {
Person person = personDao.findByPrimaryKey(country, company, fullName);
return new ModelMap("person", person);
}
private Person getPerson() {
Person person = new Person();
person.setFullName("John Doe");
person.setLastName("Doe");
person.setCompany("company1");
person.setCountry("Sweden");
person.setDescription("Test user");
return person;
}
/**
* Generates appropriate links for person leaves in the tree.
*
* @author Mattias Hellborg Arthursson
*/
private static final class PersonLinkHtmlRowLdapTreeVisitor extends HtmlRowLdapTreeVisitor {
@Override
protected String getLinkForNode(DirContextOperations node) {
String[] objectClassValues = node.getStringAttributes("objectClass");
if (containsValue(objectClassValues, "person")) {
Name dn = node.getDn();
String country = encodeValue(LdapUtils.getStringValue(dn, "c"));
String company = encodeValue(LdapUtils.getStringValue(dn, "ou"));
String fullName = encodeValue(LdapUtils.getStringValue(dn, "cn"));
return "showPerson.do?country=" + country + "&company=" + company + "&fullName=" + fullName;
}
else {
return super.getLinkForNode(node);
}
}
private String encodeValue(String value) {
try {
return URLEncoder.encode(value, "UTF8");
}
catch (UnsupportedEncodingException e) {
// Not supposed to happen
throw new RuntimeException("Unexpected encoding exception", e);
}
}
private boolean containsValue(String[] values, String value) {
for (String oneValue : values) {
if (StringUtils.equals(oneValue, value)) {
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.samples.utils;
import org.springframework.ldap.core.DirContextOperations;
import java.util.LinkedList;
import java.util.List;
public class HtmlRowLdapTreeVisitor implements LdapTreeVisitor {
private List<String> rows = new LinkedList<String>();
public void visit(DirContextOperations node, int currentDepth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < currentDepth; i++) {
sb.append("&nbsp;&nbsp;&nbsp;&nbsp;");
}
sb.append("<a href='").append(getLinkForNode(node)).append("'>").append(node.getDn()).append("</a>")
.append("<br>\n");
rows.add(sb.toString());
}
protected String getLinkForNode(DirContextOperations node) {
return "#";
}
public List<String> getRows() {
return rows;
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.samples.utils;
import org.springframework.ldap.core.DirContextOperations;
import java.util.LinkedList;
import java.util.List;
public class LdapTree {
private final DirContextOperations node;
private List<LdapTree> subContexts = new LinkedList<LdapTree>();
public LdapTree(DirContextOperations node) {
this.node = node;
}
public DirContextOperations getNode() {
return node;
}
public List<LdapTree> getSubContexts() {
return subContexts;
}
public void setSubContexts(List<LdapTree> subContexts) {
this.subContexts = subContexts;
}
public void addSubTree(LdapTree ldapTree) {
subContexts.add(ldapTree);
}
public void traverse(LdapTreeVisitor visitor) {
traverse(visitor, 0);
}
private void traverse(LdapTreeVisitor visitor, int currentDepth) {
visitor.visit(node, currentDepth);
for (LdapTree subContext : subContexts) {
subContext.traverse(visitor, currentDepth + 1);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.samples.utils;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.Name;
public class LdapTreeBuilder {
private LdapTemplate ldapTemplate;
public LdapTreeBuilder(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
public LdapTree getLdapTree(Name root) {
DirContextOperations context = ldapTemplate.lookupContext(root);
return getLdapTree(context);
}
private LdapTree getLdapTree(final DirContextOperations rootContext) {
final LdapTree ldapTree = new LdapTree(rootContext);
ldapTemplate.listBindings(rootContext.getDn(),
new AbstractContextMapper<Object>() {
@Override
protected Object doMapFromContext(DirContextOperations ctx) {
Name dn = ctx.getDn();
dn = LdapUtils.prepend(dn, rootContext.getDn());
ldapTree.addSubTree(getLdapTree(ldapTemplate
.lookupContext(dn)));
return null;
}
});
return ldapTree;
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.samples.utils;
import org.springframework.ldap.core.DirContextOperations;
public interface LdapTreeVisitor {
public void visit(DirContextOperations node, int currentDepth);
}

View File

@@ -0,0 +1,3 @@
<body>
Plain example of Spring LDAP usage.
</body>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:/ldap.properties" />
<!--
This is for test and demo purposes only - the TestContextSourceFactoryBean starts an in-process
Apache Directory Server instance and populates it with data from the specified LDIF file.
A real-world application would use a DirContextSource instead.
-->
<bean id="contextSource"
class="org.springframework.ldap.test.TestContextSourceFactoryBean">
<property name="defaultPartitionSuffix" value="dc=jayway,dc=se" />
<property name="defaultPartitionName" value="jayway" />
<property name="principal" value="${userDn}" />
<property name="password" value="${password}" />
<property name="ldifFile" value="classpath:/setup_data.ldif" />
<property name="port" value="18880" />
</bean>
<!--
Below is an example of a ContextSource definition as it would look in a real application, connecting
against an external LDAP server.
<bean class="org.springframework.ldap.core.support.LdapContextSource" id="contextSource">
<property name="url" value="ldap://ldap.example.com" />
<property name="userDn" value="cn=admin,dc=261consulting,dc=com"/>
<property name="password" value="secret"/>
<property name="base" value="dc=261consulting,dc=com" />
</bean>
-->
<bean id="ldapTemplate"
class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>
<bean id="ldapTreeBuilder"
class="org.springframework.ldap.samples.utils.LdapTreeBuilder">
<constructor-arg ref="ldapTemplate" />
</bean>
<bean id="personDao"
class="org.springframework.ldap.samples.plain.dao.PersonDaoImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
</beans>

View File

@@ -0,0 +1,4 @@
urls=ldap://127.0.0.1:18880
userDn=uid=admin,ou=system
password=secret
base=dc=jayway,dc=se

View File

@@ -0,0 +1,7 @@
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.logger.org.apache.directory=ERROR

View File

@@ -0,0 +1,35 @@
dn: c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: country
c: Sweden
description: The country of Sweden
dn: ou=company1,c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: organizationalUnit
ou: company1
description: First company in Sweden
dn: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person
userPassword: password
cn: Some Person
sn: Person
description: Sweden, Company1, Some Person
telephoneNumber: +46 555-123456
dn: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person2
userPassword: password
cn: Some Person2
sn: Person2
description: Sweden, Company1, Some Person2
telephoneNumber: +46 555-654321

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan
base-package="org.springframework.ldap.samples.plain.web" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>

View File

@@ -0,0 +1,20 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body>
<a href="showTree.do">Back</a>
<p>
Full name: ${person.fullName}
<br>
LastName: ${person.lastName}
<br>
Description: ${person.description}
<br>
Country: ${person.country}
<br>
Company: ${person.company}
<br>
Phone: ${person.phone}
<br>
</p>
</html>

View File

@@ -0,0 +1,17 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body>
<h2>Operations</h2>
<h3>Clicking a link below performs the described operation which will be reflected in the LDAP tree below</h3>
<a href="addPerson.do">Add new test person 'John Doe'</a> (only works once)<br>
<a href="updatePhoneNumber.do">Add a '0' to the phone number of test person</a> (only works if the person has been created)<br>
<a href="removePerson.do">Remove test person</a><br>
<p>
<h2>Tree contents</h2>
<h3>Click a person row to see the attribute values (country and company rows do not have additional info)</h3>
<c:forEach var="row" items="${rows}">
${row}
</c:forEach>
</p>
</body>
</html>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="Tiink-preview" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Spring LDAP Basic Example</display-name>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>basic</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/basic-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>basic</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.htm</welcome-file>
</welcome-file-list>
</web-app>

View File

@@ -0,0 +1,5 @@
<html>
<head>
<meta HTTP-EQUIV="REFRESH" content="0; url=showTree.do">
</head>
</html>

View File

@@ -0,0 +1,128 @@
/*
* 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.samples.plain.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.domain.Person;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* Abstract base class for PersonDao integration tests.
*
* @author Mattias Hellborg Arthursson
* @author Ulrik Sandberg
*/
@ContextConfiguration("/config/testContext.xml")
public class PersonDaoSampleIntegrationTest extends
AbstractJUnit4SpringContextTests {
protected Person person;
@Autowired
private PersonDao personDao;
@Before
public void preparePerson() throws Exception {
person = new Person();
person.setCountry("Sweden");
person.setCompany("company1");
person.setFullName("Some Person");
person.setLastName("Person");
person
.setDescription("Sweden, Company1, Some Person");
person.setPhone("+46 555-123456");
}
/**
* Having a single test method test create, update and delete is not exactly
* the ideal way of testing, since they depend on each other. A better way
* would be to separate the tests and load a test fixture before each
* operation, in order to guarantee the expected state every time. See the
* ldaptemplate-person sample for the correct way to do this.
*/
@Test
public void testCreateUpdateDelete() {
try {
person.setFullName("Another Person");
personDao.create(person);
personDao.findByPrimaryKey(
"Sweden", "company1",
"Another Person");
// if we got here, create succeeded
person.setDescription("Another description");
personDao.update(person);
Person result = personDao
.findByPrimaryKey(
"Sweden", "company1",
"Another Person");
assertEquals(
"Another description", result
.getDescription());
} finally {
personDao.delete(person);
try {
personDao.findByPrimaryKey(
"Sweden", "company1",
"Another Person");
fail("NameNotFoundException (when using Spring LDAP) or RuntimeException (when using traditional) expected");
} catch (NameNotFoundException expected) {
// expected
} catch (RuntimeException expected) {
// expected
}
}
}
@Test
public void testGetAllPersonNames() {
List<String> result = personDao.getAllPersonNames();
assertEquals(2, result.size());
String first = result.get(0);
assertEquals("Some Person", first);
}
@Test
public void testFindAll() {
List<Person> result = personDao.findAll();
assertEquals(2, result.size());
Person first = result.get(0);
assertEquals("Some Person", first
.getFullName());
}
@Test
public void testFindByPrimaryKey() {
Person result = personDao.findByPrimaryKey(
"Sweden", "company1", "Some Person");
assertEquals("Sweden", result.getCountry());
assertEquals("company1", result.getCompany());
assertEquals("Sweden, Company1, Some Person", result.getDescription());
assertEquals("+46 555-123456", result.getPhone());
assertEquals("Some Person", result.getFullName());
assertEquals("Person", result.getLastName());
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.samples.utils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import javax.naming.ldap.LdapName;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import static junit.framework.Assert.assertEquals;
@ContextConfiguration(locations = { "/config/testContext.xml" })
public class LdapTreeBuilderIntegrationTest extends AbstractJUnit4SpringContextTests {
@Autowired
private LdapTreeBuilder tested;
@Test
public void testGetLdapTree() {
LdapTree ldapTree = tested.getLdapTree(LdapUtils.newLdapName("c=Sweden"));
ldapTree.traverse(new TestVisitor());
}
private static final class TestVisitor implements LdapTreeVisitor {
private static final LdapName DN_1 = LdapUtils.newLdapName("c=Sweden");
private static final LdapName DN_2 = LdapUtils.newLdapName("ou=company1,c=Sweden");
private static final LdapName DN_3 = LdapUtils.newLdapName("cn=Some Person,ou=company1,c=Sweden");
private static final LdapName DN_4 = LdapUtils.newLdapName("cn=Some Person2,ou=company1,c=Sweden");
private Map<LdapName, Integer> names = new LinkedHashMap<LdapName, Integer>();
private Iterator<LdapName> keyIterator;
public TestVisitor() {
names.put(DN_1, 0);
names.put(DN_2, 1);
names.put(DN_3, 2);
names.put(DN_4, 2);
keyIterator = names.keySet().iterator();
}
public void visit(DirContextOperations node, int currentDepth) {
LdapName next = keyIterator.next();
assertEquals(next, node.getDn());
assertEquals(names.get(next).intValue(), currentDepth);
}
}
}

View File

@@ -0,0 +1,4 @@
urls=ldap://127.0.0.1:18880
userDn=uid=admin,ou=system
password=secret
base=dc=jayway,dc=se

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:/config/ldap.properties" />
</bean>
<bean id="contextSource"
class="org.springframework.ldap.test.TestContextSourceFactoryBean">
<property name="defaultPartitionSuffix" value="dc=jayway,dc=se" />
<property name="defaultPartitionName" value="jayway" />
<property name="principal" value="${userDn}" />
<property name="password" value="${password}" />
<property name="ldifFile" value="/setup_data.ldif" />
<property name="port" value="18880" />
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>
<bean id="personDao"
class="org.springframework.ldap.samples.plain.dao.PersonDaoImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
<bean class="org.springframework.ldap.samples.utils.LdapTreeBuilder">
<constructor-arg ref="ldapTemplate" />
</bean>
</beans>

View File

@@ -0,0 +1,35 @@
dn: c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: country
c: Sweden
description: The country of Sweden
dn: ou=company1,c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: organizationalUnit
ou: company1
description: First company in Sweden
dn: cn=Some Person,ou=company1,c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person
userPassword: password
cn: Some Person
sn: Person
description: Sweden, Company1, Some Person
telephoneNumber: +46 555-123456
dn: cn=Some Person2,ou=company1,c=Sweden,dc=jayway,dc=se
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person2
userPassword: password
cn: Some Person2
sn: Person2
description: Sweden, Company1, Some Person2
telephoneNumber: +46 555-654321

View File

@@ -15,6 +15,7 @@ include 'test/integration-tests-openldap'
include 'test/integration-tests-sunone'
include 'test/integration-tests-ad'
include 'samples/plain'
include 'samples/odm'
include 'samples/simple-odm'
rootProject.children.each { p->

View File

@@ -208,7 +208,7 @@ public class PersonDaoImpl implements PersonDao {
public static final String BASE_DN = "dc=example,dc=com";
...
protected Name buildDn(Person p) {
<emphasis role="bold"> return LdapNameBuilder.newInstance(BASE_DN)
<emphasis role="bold"> return LdapNameBuilder.newLdapName(BASE_DN)
.add("c", p.getCountry())
.add("ou", p.getCompany())
.add("cn", p.getFullname())

View File

@@ -296,7 +296,7 @@ public class PersonService implements PersonService, <emphasis role="bold">BaseL
}</emphasis>
...
private LdapName getFullPersonDn(Person person) {
return LdapNameBuilder.newInstance(<emphasis role="bold">basePath</emphasis>)
return LdapNameBuilder.newLdapName(<emphasis role="bold">basePath</emphasis>)
.append(person.getDn())
.build();
}

View File

@@ -305,7 +305,7 @@ public class PersonDaoImpl implements PersonDao {
}
protected Name buildDn(String fullname, String company, String country) {
return LdapNameBuilder.newInstance()
return LdapNameBuilder.newLdapName()
.add("c", country)
.add("ou", company)
.add("cn", fullname)

View File

@@ -5,172 +5,122 @@
<sect1 id="odm-intro">
<title>Introduction</title>
<para>Relational mapping frameworks like Hibernate and JPA have offered
developers the ability to use annotations to map database tables to Java
objects for some time. The Spring Framework LDAP project now offers the
same ability with respect to directories through the use of the
<code>org.springframework.ldap.odm</code> package (sometimes abbreviated
as <code>o.s.l.odm</code>).</para>
</sect1>
<sect1 id="odm-odmmanager">
<title>OdmManager</title>
<para>The <code>org.springframework.ldap.odm.OdmManager</code> interface,
and its implementation, is the central class in the ODM package. The
<code>OdmManager</code> orchestrates the process of reading objects from
the directory and mapping the data to annotated Java object classes. This
interface provides access to the underlying directory instance through the
following methods:</para>
<itemizedlist>
<listitem>
<para><code>&lt;T&gt; T read(Class&lt;T&gt; clazz, Name
dn)</code></para>
</listitem>
<listitem>
<para><code>void create(Object entry)</code></para>
</listitem>
<listitem>
<para><code>void update(Object entry)</code></para>
</listitem>
<listitem>
<para><code>void delete(Object entry)</code></para>
</listitem>
<listitem>
<para><code>&lt;T&gt; List&lt;T&gt; findAll(Class&lt;T&gt; clazz, Name
base, SearchControls searchControls)</code></para>
</listitem>
<listitem>
<para><code>&lt;T&gt; List&lt;T&gt; search(Class&lt;T&gt; clazz, Name
base, String filter, SearchControls searchControls)</code></para>
</listitem>
<listitem>
<para><code>&lt;T&gt; List&lt;T&gt; search(Class&lt;T&gt; clazz, LdapQuery query)</code></para>
</listitem>
</itemizedlist>
<para>A reference to an implementation of this interface can be obtained
through the
<code>org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean</code>.
A basic configuration of this factory would be as follows:</para>
<example>
<title>Configuring the OdmManager Factory</title>
<programlisting>
&lt;beans&gt;
...
&lt;bean id="odmManager"
class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean"&gt;
&lt;property name="converterManager" ref="converterManager" /&gt;
&lt;property name="contextSource" ref="contextSource" /&gt;
&lt;property name="managedClasses"&gt;
&lt;set&gt;
&lt;value&gt;com.example.dao.SimplePerson&lt;/value&gt;
&lt;/set&gt;
&lt;/property&gt;
&lt;/bean&gt;
...
&lt;/beans&gt;
</programlisting>
</example>
<para>The factory requires the list of entity classes to be managed by the
<code>OdmManager</code> to be explicitly declared. These classes should be
properly annotated as defined in the next section. The
<code>converterManager</code> referenced in the above definition is
described in <xref linkend="odm-typeconversion" />.</para>
<para>
Relational mapping frameworks like Hibernate and JPA have offered
developers the ability to use annotations to map database tables to Java
objects for some time. Spring LDAP project offers a similar
ability with respect to directories through the use of a number of methods:
in <literal>LdapOperations</literal>
<itemizedlist>
<listitem><literal>&lt;T&gt; T findByDn(Name dn, Class&lt;T&gt; clazz)</literal></listitem>
<listitem><literal>&lt;T&gt; T findOne(LdapQuery query, Class&lt;T&gt; clazz)</literal></listitem>
<listitem><literal>&lt;T&gt; List&lt;T&gt; find(LdapQuery query, Class&lt;T&gt; clazz)</literal></listitem>
<listitem><literal>&lt;T&gt; List&lt;T&gt; findAll(Class&lt;T&gt; clazz)</literal></listitem>
<listitem><literal>&lt;T&gt; List&lt;T&gt; findAll(Name base, SearchControls searchControls,
Class&lt;T&gt; clazz)</literal></listitem>
<listitem><literal>&lt;T&gt; List&lt;T&gt; findAll(Name base, Filter filter, SearchControls searchControls,
Class&lt;T&gt; clazz)</literal></listitem>
<listitem><literal>void create(Object entry)</literal></listitem>
<listitem><literal>void update(Object entry)</literal></listitem>
<listitem><literal>void delete(Object entry)</literal></listitem>
</itemizedlist>
</para>
</sect1>
<sect1 id="odm-annotations">
<title>Annotations</title>
<para>Entity classes managed by the <code>OdmManager</code> are required
to be annotated with the annotations in the
<code>org.springframework.ldap.odm.annotations</code> package. The
<para>Entity classes managed used with the object mapping methods are required
to be annotated with annotations from the
<literal>org.springframework.ldap.odm.annotations</literal> package. The
available annotations are:</para>
<itemizedlist>
<listitem>
<para><code>@Entry</code> - Class level annotation indicating the
<code>objectClass</code> definitions to which the entity
<para><literal>@Entry</literal> - Class level annotation indicating the
<literal>objectClass</literal> definitions to which the entity
maps.<emphasis> (required)</emphasis></para>
</listitem>
<listitem>
<para><code>@Id</code> - Indicates the entity DN; the field declaring
<para><literal>@Id</literal> - Indicates the entity DN; the field declaring
this attribute must be a derivative of the
<code>javax.naming.Name</code> class.
<literal>javax.naming.Name</literal> class.
<emphasis>(required)</emphasis></para>
</listitem>
<listitem>
<para><code>@Attribute</code> - Indicates the mapping of a directory
<para><literal>@Attribute</literal> - Indicates the mapping of a directory
attribute to the object class field.</para>
</listitem>
<listitem>
<para><code>@Transient</code> - Indicates the field is not persistent
and should be ignored by the <code>OdmManager</code>.</para>
<para><literal>@Transient</literal> - Indicates the field is not persistent
and should be ignored by the <literal>OdmManager</literal>.</para>
</listitem>
</itemizedlist>
<simpara>The <code>@Entry</code> and <code>@Id</code> attributes are
required to be declared on managed classes. <code>@Entry</code> is used to
specify which object classes the entity maps too. All object classes for
which fields are mapped are required to be declared. Also, in order for a
directory entry to be considered a match to the managed entity, all object
classes declared by the directory entry must match be declared by in the
<code>@Entry</code> annotation.</simpara>
<simpara>
The <literal>@Entry</literal> and <literal>@Id</literal> attributes are
required to be declared on managed classes.
<literal>@Entry</literal> is used to specify which object classes the entity maps to.
All object classes for which fields are mapped are required to be declared. Also, in order for a
directory entry to be considered a match to the managed entity, all object
classes declared by the directory entry must match be declared by in the
<literal>@Entry</literal> annotation. For example: let's assume that you have entries in
your LDAP tree that have the objectclasses<literal>inetOrgPerson,organizationalPerson,person,top</literal>.
If you are only interested in changing the attributes defined in the <literal>person</literal>
objectclass, your <literal>@Entry</literal> annotation can be
<literal>@Entry(objectClasses = { "person", "top" })</literal>. However, if you want to manage
attributes defined in the <literal>inetOrgPerson</literal> objectclass you'll need to use the full
monty: <literal>@Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" })</literal>.
</simpara>
<simpara>The <code>@Id</code> annotation is used to map the distinguished
<simpara>The <literal>@Id</literal> annotation is used to map the distinguished
name of the entry to a field. The field must be an instance of
<code>javax.naming.Name</code> or a subclass of it.</simpara>
<literal>javax.naming.Name</literal>.</simpara>
<simpara>The <code>@Attribute</code> annotation is used to map object
class fields to entity fields. <code>@Attribute</code> is required to
<simpara>The <literal>@Attribute</literal> annotation is used to map object
class fields to entity fields. <literal>@Attribute</literal> is required to
declare the name of the object class property to which the field maps and
may optionally declare the syntax OID of the LDAP attribute, to guarantee
exact matching. <code>@Attribute</code> also provides the type declaration
exact matching. <literal>@Attribute</literal> also provides the type declaration
which allows you to indicate whether the attribute is regarded as binary
based or string based by the LDAP JNDI provider.</simpara>
<simpara>The <code>@Transient</code> annotation is used to indicate the
field should be ignored by the <code>OdmManager</code> and not mapped to
<simpara>The <literal>@Transient</literal> annotation is used to indicate the
field should be ignored by the object directory mapping and not mapped to
an underlying LDAP property.</simpara>
</sect1>
<sect1 id="odm-typeconversion">
<title>Type Conversion</title>
<para>The <code>OdmManager</code> relies on the
<code>org.springframework.ldap.odm.typeconversion</code> package to
convert LDAP attributes to Java fields. The main interface in this class
is the
<code>org.springframework.ldap.odm.typeconversion.ConverterManager</code>.
The default <code>ConverterManager</code> implementation uses the
<para>The object directory mapping relies on the
<literal>org.springframework.ldap.odm.typeconversion</literal> package to
convert LDAP attributes to Java fields. For simple setups, no particular configuraion
is needed for this purpose. However, more complex mapping scenarios require the
<literal>ObjectDirectoryMapper</literal> and its associated <literal>ConverterManager</literal>
to be explicitly configured on the <literal>LdapTemplate</literal> instance.
The default <literal>ConverterManager</literal> implementation uses the
following algorithm when parsing objects to convert fields:<orderedlist>
<listitem>
<para>Try to find and use a <code>Converter</code> registered for
the <code>fromClass</code>, <code>syntax</code> and
<code>toClass</code> and use it.</para>
<para>Try to find and use a <literal>Converter</literal> registered for
the <literal>fromClass</literal>, <literal>syntax</literal> and
<literal>toClass</literal> and use it.</para>
</listitem>
<listitem>
<para>If this fails, then if the <code>toClass</code>
<code>isAssignableFrom</code> the
<code>fromClass</code> then just assign it.</para>
<para>If this fails, then if the <literal>toClass</literal>
<literal>isAssignableFrom</literal> the
<literal>fromClass</literal> then just assign it.</para>
</listitem>
<listitem>
<para>If this fails try to find and use a
<code>Converter</code> registered for the
<code>fromClass</code> and the <code>toClass</code> ignoring the
<literal>Converter</literal> registered for the
<literal>fromClass</literal> and the <literal>toClass</literal> ignoring the
syntax.</para>
</listitem>
@@ -252,6 +202,17 @@
&lt;/set&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="ldapTemplate"
class="org.springframework.ldap.core.LdapTemplate"&gt;
&lt;property name="objectDirectoryMapper"&gt;
&lt;bean class="org.springframework.ldap.odm.impl.DefaultObjectDirectoryMapper"&gt;
&lt;property name="converterManager" ref="converterManager" /&gt;
&lt;/bean&gt;
&lt;/property&gt;
&lt;!-- More configuration of LdapTemplate here --&gt;
&lt;/bean&gt;
</programlisting>
</example>
</sect1>
@@ -259,31 +220,47 @@
<sect1 id="odm-execution">
<title>Execution</title>
<para>After all components are configured, directory interaction can be
achieved through a reference to the <code>OdmManager</code>, as shown in
this example:</para>
<para>
When all components have been properly configured and annotated, the object mapping
methods of <literal>LdapTemplate</literal> can be used as follows:</para>
<example>
<title>Execution</title>
<programlisting>
public class App {
private static Log log = LogFactory.getLog(App.class);
private static final SearchControls searchControls =
new SearchControls(SearchControls.SUBTREE_SCOPE, 100, 10000, null, true, false);
public static void main( String[] args ) {
try {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
OdmManager manager = (OdmManager) context.getBean("odmManager");
List&lt;SimplePerson&gt; people = manager.search(SimplePerson.class,
LdapUtils.newLdapName("dc=example,dc=com"), "uid=*", searchControls);
log.info("People found: " + people.size());
for (SimplePerson person : people) {
log.info( person );
}
} catch (Exception e) {
e.printStackTrace();
}
public class OdmPersonDao {
@Autowired
private LdapTemplate ldapTemplate;
public Person create(Person person) {
person.setDn(buildDn(person));
ldapTemplate.create(person);
return person;
}
private Name buildDn(Person person) {
// build a distinguished name based on a person.
}
public Person findByUid(String uid) {
return ldapTemplate.findOne(query().where("uid").is(uid), Person.class);
}
public void update(Person person) {
// Requires that the Person was originally retrieved from this Dao, i.e. that the Dn is populated.
ldapTemplate.update(person);
}
public void delete(Person person) {
// Requires that the Person was originally retrieved from this Dao, i.e. that the Dn is populated.
ldapTemplate.delete(person);
}
public List&gt;Person&lt; findAll() {
return ldapTemplate.findAll(Person.class);
}
public List&gt;Person&lt; findByLastName(String lastName) {
return ldapTemplate.find(query().where("sn").is(lastName), Person.class);
}
}
</programlisting>

View File

@@ -0,0 +1,80 @@
package org.springframework.ldap.itest.odm;
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;
import java.util.List;
/**
* @author Mattias Hellborg Arthursson
*/
@Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" })
public class Person {
@Id
private Name dn;
@Attribute(name = "cn")
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;
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;
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.dao.EmptyResultDataAccessException;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.itest.AbstractLdapTemplateIntegrationTest;
import org.springframework.ldap.odm.core.OdmException;
import org.springframework.ldap.support.LdapNameBuilder;
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.assertTrue;
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 LdapTemplateOdmITest extends AbstractLdapTemplateIntegrationTest {
@Autowired
private LdapTemplate tested;
@Test
public void testFindOne() {
Person person = tested.findOne(query()
.where("cn").is("Some Person3"), Person.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());
}
@Test
public void testFindByDn() {
Person person = tested.findByDn(LdapUtils.newLdapName("cn=Some Person3,ou=company1,c=Sweden"), Person.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());
}
@Test(expected = OdmException.class)
public void testFindByDnThrowsExceptionOnInvalidEntry() {
tested.findByDn(LdapUtils.newLdapName("ou=company1,c=Sweden"), Person.class);
}
@Test(expected = EmptyResultDataAccessException.class)
public void testFindOneThrowsEmptyResultIfNotFound() {
tested.findOne(query()
.where("cn").is("This cn does not exist"), Person.class);
}
@Test
public void testFind() {
List<Person> persons = tested.find(query()
.where("cn").is("Some Person3"), Person.class);
assertEquals(1, persons.size());
Person person = persons.get(0);
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());
}
@Test
public void testFindInCountry() {
List<Person> persons = tested.find(query()
.base("c=Sweden")
.where("cn").isPresent(), Person.class);
assertEquals(4, persons.size());
Person person = persons.get(0);
assertNotNull(person);
}
@Test
public void testFindAll() {
List<Person> result = tested.findAll(Person.class);
assertEquals(5, result.size());
}
@Test
public void testCreate() {
Person person = new Person();
person.setDn(LdapNameBuilder.newLdapName("ou=company1,c=Sweden")
.add("cn", "New Person").build());
person.setCommonName("New Person");
person.setSurname("Person");
person.setDesc(Arrays.asList("This is the description"));
person.setTelephoneNumber("0123456");
tested.create(person);
assertEquals(6, tested.findAll(Person.class).size());
person = tested.findOne(query()
.where("cn").is("New Person"), Person.class);
assertEquals("New Person", person.getCommonName());
assertEquals("Person", person.getSurname());
assertEquals("This is the description", person.getDesc().get(0));
assertEquals("0123456", person.getTelephoneNumber());
}
@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);
}
}
}