From 1b814e36310df14d64971c4bea918fa3d274a179 Mon Sep 17 00:00:00 2001 From: Mattias Hellborg Arthursson Date: Fri, 27 Sep 2013 10:43:07 +0200 Subject: [PATCH] LDAP-265: Moved ODM functionality to core and added methods in LdapTemplate. --- .../org/springframework/LdapDataEntry.java | 178 +++++++ .../ldap/core/DirContextOperations.java | 170 +------ .../ldap/core/LdapOperations.java | 117 +++++ .../ldap/core/LdapTemplate.java | 138 +++++- .../ldap/odm/annotations/Attribute.java | 0 .../ldap/odm/annotations/Entry.java | 0 .../ldap/odm/annotations/Id.java | 0 .../ldap/odm/annotations/Transient.java | 0 .../ldap/odm/annotations/package-info.java | 0 .../ldap/odm/core/ObjectDirectoryMapper.java | 78 ++++ .../ldap/odm/core/OdmException.java | 0 .../ldap/odm/core/impl/AttributeMetaData.java | 0 .../ldap/odm/core/impl/CaseIgnoreString.java | 0 .../impl/DefaultObjectDirectoryMapper.java | 367 +++++++++++++++ .../odm/core/impl/InvalidEntryException.java | 0 .../ldap/odm/core/impl/MetaDataException.java | 0 .../ldap/odm/core/impl/ObjectMetaData.java | 0 .../core/impl/UnmanagedClassException.java | 0 .../ldap/odm/core/impl/package-info.java | 0 .../ldap/odm/core/package-info.java | 0 .../typeconversion/ConverterException.java | 0 .../odm/typeconversion/ConverterManager.java | 0 .../odm/typeconversion/impl/Converter.java | 0 .../impl/ConverterManagerFactoryBean.java | 0 .../impl/ConverterManagerImpl.java | 6 +- .../impl/converters/FromStringConverter.java | 0 .../impl/converters/ToStringConverter.java | 0 .../impl/converters/package-info.java | 0 .../odm/typeconversion/impl/package-info.java | 0 .../ldap/odm/typeconversion/package-info.java | 0 .../ldap/support/LdapNameBuilder.java | 4 +- .../ldap/odm/core/OdmManager.java | 1 + .../ldap/odm/core/impl/OdmManagerImpl.java | 437 ++---------------- .../core/impl/OdmManagerImplFactoryBean.java | 1 + .../ldap/odm/test/TestLdap.java | 3 +- samples/odm/build.gradle | 16 + samples/odm/readme.txt | 9 + .../ldap/samples/plain/dao/PersonDao.java | 41 ++ .../ldap/samples/plain/dao/PersonDaoImpl.java | 111 +++++ .../ldap/samples/plain/domain/Person.java | 128 +++++ .../samples/plain/web/DefaultController.java | 130 ++++++ .../samples/utils/HtmlRowLdapTreeVisitor.java | 48 ++ .../ldap/samples/utils/LdapTree.java | 61 +++ .../ldap/samples/utils/LdapTreeBuilder.java | 55 +++ .../ldap/samples/utils/LdapTreeVisitor.java | 24 + samples/odm/src/main/java/overview.html | 3 + .../src/main/resources/applicationContext.xml | 52 +++ .../odm/src/main/resources/ldap.properties | 4 + .../odm/src/main/resources/log4j.properties | 7 + .../odm/src/main/resources/setup_data.ldif | 35 ++ .../src/main/webapp/WEB-INF/basic-servlet.xml | 17 + .../main/webapp/WEB-INF/jsp/showPerson.jsp | 20 + .../src/main/webapp/WEB-INF/jsp/showTree.jsp | 17 + samples/odm/src/main/webapp/WEB-INF/web.xml | 39 ++ samples/odm/src/main/webapp/index.htm | 5 + .../dao/PersonDaoSampleIntegrationTest.java | 128 +++++ .../utils/LdapTreeBuilderIntegrationTest.java | 71 +++ .../src/test/resources/config/ldap.properties | 4 + .../src/test/resources/config/testContext.xml | 33 ++ .../odm/src/test/resources/setup_data.ldif | 35 ++ settings.gradle | 1 + src/docbkx/basic.xml | 2 +- src/docbkx/configuration.xml | 2 +- src/docbkx/dirobjectfactory.xml | 2 +- src/docbkx/odm.xml | 255 +++++----- .../ldap/itest/odm/Person.java | 80 ++++ .../ldap/itest/odm/LdapTemplateOdmITest.java | 167 +++++++ 67 files changed, 2374 insertions(+), 728 deletions(-) create mode 100644 core/src/main/java/org/springframework/LdapDataEntry.java rename {odm => core}/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/annotations/Entry.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/annotations/Id.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/annotations/Transient.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/annotations/package-info.java (100%) create mode 100644 core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/OdmException.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/impl/CaseIgnoreString.java (100%) create mode 100644 core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/impl/InvalidEntryException.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/impl/MetaDataException.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/impl/UnmanagedClassException.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/impl/package-info.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/core/package-info.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterException.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterManager.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/impl/Converter.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java (97%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/package-info.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/impl/package-info.java (100%) rename {odm => core}/src/main/java/org/springframework/ldap/odm/typeconversion/package-info.java (100%) create mode 100644 samples/odm/build.gradle create mode 100644 samples/odm/readme.txt create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDao.java create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/plain/web/DefaultController.java create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/utils/HtmlRowLdapTreeVisitor.java create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTree.java create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeBuilder.java create mode 100644 samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeVisitor.java create mode 100644 samples/odm/src/main/java/overview.html create mode 100644 samples/odm/src/main/resources/applicationContext.xml create mode 100644 samples/odm/src/main/resources/ldap.properties create mode 100644 samples/odm/src/main/resources/log4j.properties create mode 100644 samples/odm/src/main/resources/setup_data.ldif create mode 100644 samples/odm/src/main/webapp/WEB-INF/basic-servlet.xml create mode 100755 samples/odm/src/main/webapp/WEB-INF/jsp/showPerson.jsp create mode 100644 samples/odm/src/main/webapp/WEB-INF/jsp/showTree.jsp create mode 100644 samples/odm/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/odm/src/main/webapp/index.htm create mode 100644 samples/odm/src/test/java/org/springframework/ldap/samples/plain/dao/PersonDaoSampleIntegrationTest.java create mode 100644 samples/odm/src/test/java/org/springframework/ldap/samples/utils/LdapTreeBuilderIntegrationTest.java create mode 100644 samples/odm/src/test/resources/config/ldap.properties create mode 100644 samples/odm/src/test/resources/config/testContext.xml create mode 100644 samples/odm/src/test/resources/setup_data.ldif create mode 100644 test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java create mode 100644 test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmITest.java diff --git a/core/src/main/java/org/springframework/LdapDataEntry.java b/core/src/main/java/org/springframework/LdapDataEntry.java new file mode 100644 index 00000000..bb1e882b --- /dev/null +++ b/core/src/main/java/org/springframework/LdapDataEntry.java @@ -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, null will be returned. + * + * @param name name of the attribute. + * @return the value of the attribute if it exists, or null 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, null will be returned. + * + * @param name name of the attribute. + * @return the attribute value as an object if it exists, or + * null 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 true if the attribute exists, false + * otherwise + */ + boolean attributeExists(String name); + + /** + * Set the with the name name to the value. + * + * @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 true, 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 addIfDuplicateExists + * parameter controls the handling of duplicates. It false, + * 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 true will add the value + * regardless of whether there is an identical value already, allowing for + * duplicate attribute values; false 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 null + * 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 null otherwise. + * @since 1.3 + */ + Object[] getObjectAttributes(String name); + + /** + * Get all String values of the attribute as a SortedSet. + * + * @param name name of the attribute. + * @return a SortedSet containing all values of the attribute, + * or null if the attribute does not exist. + * @throws IllegalArgumentException if one of the found attribute values cannot be cast to a String. + */ + SortedSet getAttributeSortedStringSet(String name); + + /** + * Returns the DN relative to the base path. + * NB: 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(); +} diff --git a/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java b/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java index 22acbf0f..5b15408a 100644 --- a/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java +++ b/core/src/main/java/org/springframework/ldap/core/DirContextOperations.java @@ -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, null will be returned. - * - * @param name name of the attribute. - * @return the value of the attribute if it exists, or null 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, null will be returned. - * - * @param name name of the attribute. - * @return the attribute value as an object if it exists, or - * null 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 true if the attribute exists, false - * otherwise - */ - boolean attributeExists(String name); - - /** - * Set the with the name name to the value. - * - * @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 true, 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 addIfDuplicateExists - * parameter controls the handling of duplicates. It false, - * 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 true will add the value - * regardless of whether there is an identical value already, allowing for - * duplicate attribute values; false 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 ( * getStringAttribute 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 null - * 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 null otherwise. - * @since 1.3 - */ - Object[] getObjectAttributes(String name); - - /** - * Get all String values of the attribute as a SortedSet. - * - * @param name name of the attribute. - * @return a SortedSet containing all values of the attribute, - * or null if the attribute does not exist. - * @throws IllegalArgumentException if one of the found attribute values cannot be cast to a String. - */ - SortedSet getAttributeSortedStringSet(String name); - - /** - * Returns the DN relative to the base path. - * NB: 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(); } diff --git a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java index 2089fa0f..4277e1eb 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java @@ -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 searchForObject(LdapQuery query, ContextMapper mapper); + + /** + * Read a named entry from the LDAP directory. + * + * @param 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 findByDn(Name dn, Class clazz); + + /** + * Create the given entry in the LDAP directory. + * + * @param entry The entry to be create, it must not 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 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 + */ + List findAll(Class clazz); + + /** + * Find all entries in the LDAP directory of a given type. + * + * @param 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 + */ + List findAll(Name base, SearchControls searchControls, Class clazz); + + /** + * Find all entries in the LDAP directory of a given type that matches the specified filter. + * + * @param 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 List find(Name base, Filter filter, SearchControls searchControls, Class clazz); + + /** + * Search for entries in the LDAP directory. + *

+ * Only those entries that both match the query search filter and + * are represented by the given Java class are returned. + * + * @param 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 + */ + List find(LdapQuery query, Class clazz); + + /** + * + * @param query + * @param clazz + * @param + * @return + * @since 2.0 + */ + T findOne(LdapQuery query, Class clazz); } diff --git a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java index 45445069..e5642ff2 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -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 findByDn(Name dn, final Class 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() { + @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 List findAll(Name base, SearchControls searchControls, final Class clazz) { + return find(base, null, searchControls, clazz); + } + + @Override + public List findAll(Class clazz) { + return findAll(LdapUtils.emptyLdapName(), + getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, ALL_ATTRIBUTES), + clazz); + } + + @Override + public List find(Name base, Filter filter, SearchControls searchControls, final Class 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 result = search(localBase, finalFilter.encode(), searchControls, new ContextMapper() { + @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 List find(LdapQuery query, Class clazz) { + SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); + return find(query.base(), query.filter(), searchControls, clazz); + } + + @Override + public T findOne(LdapQuery query, Class clazz) { + List 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); + } } diff --git a/odm/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java b/core/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java rename to core/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/annotations/Entry.java b/core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/annotations/Entry.java rename to core/src/main/java/org/springframework/ldap/odm/annotations/Entry.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/annotations/Id.java b/core/src/main/java/org/springframework/ldap/odm/annotations/Id.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/annotations/Id.java rename to core/src/main/java/org/springframework/ldap/odm/annotations/Id.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/annotations/Transient.java b/core/src/main/java/org/springframework/ldap/odm/annotations/Transient.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/annotations/Transient.java rename to core/src/main/java/org/springframework/ldap/odm/annotations/Transient.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/annotations/package-info.java b/core/src/main/java/org/springframework/ldap/odm/annotations/package-info.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/annotations/package-info.java rename to core/src/main/java/org/springframework/ldap/odm/annotations/package-info.java diff --git a/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java new file mode 100644 index 00000000..719baaa9 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java @@ -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 mapFromLdapDataEntry(LdapDataEntry ctx, Class 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); +} diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/OdmException.java b/core/src/main/java/org/springframework/ldap/odm/core/OdmException.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/OdmException.java rename to core/src/main/java/org/springframework/ldap/odm/core/OdmException.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java rename to core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/CaseIgnoreString.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/CaseIgnoreString.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/impl/CaseIgnoreString.java rename to core/src/main/java/org/springframework/ldap/odm/core/impl/CaseIgnoreString.java diff --git a/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java new file mode 100644 index 00000000..dbd7de5f --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java @@ -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 <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, EntityData> metaDataMap=new ConcurrentHashMap, 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 targetClass = (attributeInfo.isBinary()) ? byte[].class : String.class; + // Multi valued? + if (!attributeInfo.isList()) { + // Single valued - get the value of the field + Object fieldValue = field.get(entry); + // Ignore null field values + if (fieldValue != null) { + // Convert the field value to the required type and write it into the JNDI context + context.setAttributeValue(attributeInfo.getName().toString(), converterManager.convert(fieldValue, + attributeInfo.getSyntax(), targetClass)); + } + } else { // Multi-valued + // We need to build up a list of of the values + List attributeValues = new ArrayList(); + // Get the list of values + Collection fieldValues = (Collection)field.get(entry); + // Ignore null lists + if (fieldValues != null) { + for (final Object o : fieldValues) { + // Ignore null values + if (o != null) { + attributeValues.add((String)converterManager.convert(o, attributeInfo.getSyntax(), + targetClass)); + } + } + context.setAttributeValues(attributeInfo.getName().toString(), attributeValues.toArray()); + } + } + } catch (IllegalAccessException e) { + throw new InvalidEntryException(String.format("Can't set attribute %1$s", attributeInfo.getName()), + e); + } + } + } + } + + @Override + public T mapFromLdapDataEntry(LdapDataEntry context, Class 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 attributeValueMap = new HashMap(); + // Get a NamingEnumeration to loop through the JNDI attributes in the entry + Attributes attributes = context.getAttributes(); + NamingEnumeration 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 fieldValues = new ArrayList(); + // Grab the attribute from the JNDI representation + Attribute currentAttribute = attributeValueMap.get(attributeInfo.getName()); + // There is no guarantee that this attribute is present in the directory - so ignore nulls + if (currentAttribute != null) { + // Loop through the values of the JNDI attribute + NamingEnumeration valuesEmumeration = currentAttribute.getAll(); + while (valuesEmumeration.hasMore()) { + // Get the current value + Object value = valuesEmumeration.nextElement(); + // Check the value is not null + if (value != null) { + // Convert the value to its Java representation and add it to our working list + fieldValues.add(converterManager.convert(value, attributeInfo.getSyntax(), + attributeInfo.getValueClass())); + } + } + } + // Now we need to set the List in to a Java object + field.set(result, fieldValues); + } + } 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 objectClassesFromJndi = new HashSet(); + 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; + } +} diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/InvalidEntryException.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/InvalidEntryException.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/impl/InvalidEntryException.java rename to core/src/main/java/org/springframework/ldap/odm/core/impl/InvalidEntryException.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/MetaDataException.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/MetaDataException.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/impl/MetaDataException.java rename to core/src/main/java/org/springframework/ldap/odm/core/impl/MetaDataException.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java rename to core/src/main/java/org/springframework/ldap/odm/core/impl/ObjectMetaData.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/UnmanagedClassException.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/UnmanagedClassException.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/impl/UnmanagedClassException.java rename to core/src/main/java/org/springframework/ldap/odm/core/impl/UnmanagedClassException.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/package-info.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/package-info.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/impl/package-info.java rename to core/src/main/java/org/springframework/ldap/odm/core/impl/package-info.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/package-info.java b/core/src/main/java/org/springframework/ldap/odm/core/package-info.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/core/package-info.java rename to core/src/main/java/org/springframework/ldap/odm/core/package-info.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterException.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterException.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterException.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterException.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterManager.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterManager.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterManager.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/ConverterManager.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/Converter.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/Converter.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/Converter.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/Converter.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerFactoryBean.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java similarity index 97% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java index 2ce4adb5..ab347971 100755 --- a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java +++ b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/ConverterManagerImpl.java @@ -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}. *

diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/FromStringConverter.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/ToStringConverter.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/package-info.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/package-info.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/package-info.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/converters/package-info.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/package-info.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/package-info.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/impl/package-info.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/impl/package-info.java diff --git a/odm/src/main/java/org/springframework/ldap/odm/typeconversion/package-info.java b/core/src/main/java/org/springframework/ldap/odm/typeconversion/package-info.java similarity index 100% rename from odm/src/main/java/org/springframework/ldap/odm/typeconversion/package-info.java rename to core/src/main/java/org/springframework/ldap/odm/typeconversion/package-info.java diff --git a/core/src/main/java/org/springframework/ldap/support/LdapNameBuilder.java b/core/src/main/java/org/springframework/ldap/support/LdapNameBuilder.java index f851c801..48d6ebdb 100644 --- a/core/src/main/java/org/springframework/ldap/support/LdapNameBuilder.java +++ b/core/src/main/java/org/springframework/ldap/support/LdapNameBuilder.java @@ -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)); } diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/OdmManager.java b/odm/src/main/java/org/springframework/ldap/odm/core/OdmManager.java index dc8d52b7..3930f53f 100755 --- a/odm/src/main/java/org/springframework/ldap/odm/core/OdmManager.java +++ b/odm/src/main/java/org/springframework/ldap/odm/core/OdmManager.java @@ -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 { diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImpl.java b/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImpl.java index bc2811f4..61cc2659 100755 --- a/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImpl.java +++ b/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImpl.java @@ -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 <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, EntityData> metaDataMap=new HashMap, EntityData>(); + private DefaultObjectDirectoryMapper objectDirectoryMapper; public OdmManagerImpl(ConverterManager converterManager, LdapOperations ldapOperations, Set> 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 read(Class 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(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 List search(Class 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 result = ldapTemplate.search(localBase, finalFilter, scope, new GenericContextMapper(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 List search(Class 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 List findAll(Class 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 targetClass = (attributeInfo.isBinary()) ? byte[].class : String.class; - // Multi valued? - if (!attributeInfo.isList()) { - // Single valued - get the value of the field - Object fieldValue = field.get(entry); - // Ignore null field values - if (fieldValue != null) { - // Convert the field value to the required type and write it into the JNDI context - context.setAttributeValue(attributeInfo.getName().toString(), converterManager.convert(fieldValue, - attributeInfo.getSyntax(), targetClass)); - } - } else { // Multi-valued - // We need to build up a list of of the values - List attributeValues = new ArrayList(); - // Get the list of values - Collection fieldValues = (Collection)field.get(entry); - // Ignore null lists - if (fieldValues != null) { - for (final Object o : fieldValues) { - // Ignore null values - if (o != null) { - attributeValues.add((String)converterManager.convert(o, attributeInfo.getSyntax(), - targetClass)); - } - } - context.setAttributeValues(attributeInfo.getName().toString(), attributeValues.toArray()); - } - } - } 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 implements ParameterizedContextMapper { - private final Class managedClass; - - private GenericContextMapper(Class 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 attributeValueMap = new HashMap(); - // Get a NamingEnumeration to loop through the JNDI attributes in the entry - Attributes attributes = context.getAttributes(); - NamingEnumeration 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 fieldValues = new ArrayList(); - // Grab the attribute from the JNDI representation - Attribute currentAttribute = attributeValueMap.get(attributeInfo.getName()); - // There is no guarantee that this attribute is present in the directory - so ignore nulls - if (currentAttribute != null) { - // Loop through the values of the JNDI attribute - NamingEnumeration valuesEmumeration = currentAttribute.getAll(); - while (valuesEmumeration.hasMore()) { - // Get the current value - Object value = valuesEmumeration.nextElement(); - // Check the value is not null - if (value != null) { - // Convert the value to its Java representation and add it to our working list - fieldValues.add(converterManager.convert(value, attributeInfo.getSyntax(), - attributeInfo.getValueClass())); - } - } - } - // Now we need to set the List in to a Java object - field.set(result, fieldValues); - } - } 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 objectClassesFromJndi = new HashSet(); - 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; - } - } diff --git a/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImplFactoryBean.java b/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImplFactoryBean.java index c4250373..6d38578c 100755 --- a/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImplFactoryBean.java +++ b/odm/src/main/java/org/springframework/ldap/odm/core/impl/OdmManagerImplFactoryBean.java @@ -43,6 +43,7 @@ import java.util.Set; * * * @author Paul Harvey <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; diff --git a/odm/src/test/java/org/springframework/ldap/odm/test/TestLdap.java b/odm/src/test/java/org/springframework/ldap/odm/test/TestLdap.java index 7286385c..12f60f37 100755 --- a/odm/src/test/java/org/springframework/ldap/odm/test/TestLdap.java +++ b/odm/src/test/java/org/springframework/ldap/odm/test/TestLdap.java @@ -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); } diff --git a/samples/odm/build.gradle b/samples/odm/build.gradle new file mode 100644 index 00000000..c3f27017 --- /dev/null +++ b/samples/odm/build.gradle @@ -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" +} \ No newline at end of file diff --git a/samples/odm/readme.txt b/samples/odm/readme.txt new file mode 100644 index 00000000..4d1191b5 --- /dev/null +++ b/samples/odm/readme.txt @@ -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 diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDao.java b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDao.java new file mode 100644 index 00000000..c828b876 --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDao.java @@ -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 getAllPersonNames(); + + List findAll(); + + Person findByPrimaryKey(String country, String company, String fullname); +} diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java new file mode 100644 index 00000000..c4560155 --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java @@ -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 getAllPersonNames() { + return ldapTemplate.search(query() + .attributes("cn") + .where("objectclass").is("person"), + new AttributesMapper() { + public String mapFromAttributes(Attributes attrs) throws NamingException { + return attrs.get("cn").get().toString(); + } + }); + } + + @Override + public List 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; + } +} diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java new file mode 100644 index 00000000..c3043903 --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/domain/Person.java @@ -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); + } +} diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/plain/web/DefaultController.java b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/web/DefaultController.java new file mode 100644 index 00000000..8aef5fbf --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/plain/web/DefaultController.java @@ -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; + } + } + +} diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/utils/HtmlRowLdapTreeVisitor.java b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/HtmlRowLdapTreeVisitor.java new file mode 100644 index 00000000..c98dd094 --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/HtmlRowLdapTreeVisitor.java @@ -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 rows = new LinkedList(); + + public void visit(DirContextOperations node, int currentDepth) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < currentDepth; i++) { + sb.append("    "); + } + + sb.append("").append(node.getDn()).append("") + .append("
\n"); + + rows.add(sb.toString()); + } + + protected String getLinkForNode(DirContextOperations node) { + return "#"; + } + + public List getRows() { + return rows; + } + +} diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTree.java b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTree.java new file mode 100644 index 00000000..0919b4da --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTree.java @@ -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 subContexts = new LinkedList(); + + public LdapTree(DirContextOperations node) { + this.node = node; + } + + public DirContextOperations getNode() { + return node; + } + + public List getSubContexts() { + return subContexts; + } + + public void setSubContexts(List 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); + } + } +} diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeBuilder.java b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeBuilder.java new file mode 100644 index 00000000..633c12e1 --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeBuilder.java @@ -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() { + @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; + } +} diff --git a/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeVisitor.java b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeVisitor.java new file mode 100644 index 00000000..5032f9a7 --- /dev/null +++ b/samples/odm/src/main/java/org/springframework/ldap/samples/utils/LdapTreeVisitor.java @@ -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); +} diff --git a/samples/odm/src/main/java/overview.html b/samples/odm/src/main/java/overview.html new file mode 100644 index 00000000..d0a7abb8 --- /dev/null +++ b/samples/odm/src/main/java/overview.html @@ -0,0 +1,3 @@ + +Plain example of Spring LDAP usage. + \ No newline at end of file diff --git a/samples/odm/src/main/resources/applicationContext.xml b/samples/odm/src/main/resources/applicationContext.xml new file mode 100644 index 00000000..55561d29 --- /dev/null +++ b/samples/odm/src/main/resources/applicationContext.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/odm/src/main/resources/ldap.properties b/samples/odm/src/main/resources/ldap.properties new file mode 100644 index 00000000..58dd2aab --- /dev/null +++ b/samples/odm/src/main/resources/ldap.properties @@ -0,0 +1,4 @@ +urls=ldap://127.0.0.1:18880 +userDn=uid=admin,ou=system +password=secret +base=dc=jayway,dc=se diff --git a/samples/odm/src/main/resources/log4j.properties b/samples/odm/src/main/resources/log4j.properties new file mode 100644 index 00000000..5ce7a3ee --- /dev/null +++ b/samples/odm/src/main/resources/log4j.properties @@ -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 \ No newline at end of file diff --git a/samples/odm/src/main/resources/setup_data.ldif b/samples/odm/src/main/resources/setup_data.ldif new file mode 100644 index 00000000..e6d7dce6 --- /dev/null +++ b/samples/odm/src/main/resources/setup_data.ldif @@ -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 diff --git a/samples/odm/src/main/webapp/WEB-INF/basic-servlet.xml b/samples/odm/src/main/webapp/WEB-INF/basic-servlet.xml new file mode 100644 index 00000000..31857dd7 --- /dev/null +++ b/samples/odm/src/main/webapp/WEB-INF/basic-servlet.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/samples/odm/src/main/webapp/WEB-INF/jsp/showPerson.jsp b/samples/odm/src/main/webapp/WEB-INF/jsp/showPerson.jsp new file mode 100755 index 00000000..4d25b21b --- /dev/null +++ b/samples/odm/src/main/webapp/WEB-INF/jsp/showPerson.jsp @@ -0,0 +1,20 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + + +Back +

+ +Full name: ${person.fullName} +
+LastName: ${person.lastName} +
+Description: ${person.description} +
+Country: ${person.country} +
+Company: ${person.company} +
+Phone: ${person.phone} +
+

+ diff --git a/samples/odm/src/main/webapp/WEB-INF/jsp/showTree.jsp b/samples/odm/src/main/webapp/WEB-INF/jsp/showTree.jsp new file mode 100644 index 00000000..80546b96 --- /dev/null +++ b/samples/odm/src/main/webapp/WEB-INF/jsp/showTree.jsp @@ -0,0 +1,17 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + + +

Operations

+

Clicking a link below performs the described operation which will be reflected in the LDAP tree below

+Add new test person 'John Doe' (only works once)
+Add a '0' to the phone number of test person (only works if the person has been created)
+Remove test person
+

+

Tree contents

+

Click a person row to see the attribute values (country and company rows do not have additional info)

+ + ${row} + +

+ + diff --git a/samples/odm/src/main/webapp/WEB-INF/web.xml b/samples/odm/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..1c7d6a7d --- /dev/null +++ b/samples/odm/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,39 @@ + + + + Spring LDAP Basic Example + + + org.springframework.web.context.ContextLoaderListener + + + + + contextConfigLocation + classpath:/applicationContext.xml + + + + basic + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/basic-servlet.xml + + 1 + + + + basic + *.do + + + + index.htm + + diff --git a/samples/odm/src/main/webapp/index.htm b/samples/odm/src/main/webapp/index.htm new file mode 100644 index 00000000..c4a3f6f3 --- /dev/null +++ b/samples/odm/src/main/webapp/index.htm @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/odm/src/test/java/org/springframework/ldap/samples/plain/dao/PersonDaoSampleIntegrationTest.java b/samples/odm/src/test/java/org/springframework/ldap/samples/plain/dao/PersonDaoSampleIntegrationTest.java new file mode 100644 index 00000000..fd2bcb43 --- /dev/null +++ b/samples/odm/src/test/java/org/springframework/ldap/samples/plain/dao/PersonDaoSampleIntegrationTest.java @@ -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 result = personDao.getAllPersonNames(); + assertEquals(2, result.size()); + String first = result.get(0); + assertEquals("Some Person", first); + } + + @Test + public void testFindAll() { + List 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()); + } +} diff --git a/samples/odm/src/test/java/org/springframework/ldap/samples/utils/LdapTreeBuilderIntegrationTest.java b/samples/odm/src/test/java/org/springframework/ldap/samples/utils/LdapTreeBuilderIntegrationTest.java new file mode 100644 index 00000000..e29ad216 --- /dev/null +++ b/samples/odm/src/test/java/org/springframework/ldap/samples/utils/LdapTreeBuilderIntegrationTest.java @@ -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 names = new LinkedHashMap(); + + private Iterator 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); + } + } + +} diff --git a/samples/odm/src/test/resources/config/ldap.properties b/samples/odm/src/test/resources/config/ldap.properties new file mode 100644 index 00000000..58dd2aab --- /dev/null +++ b/samples/odm/src/test/resources/config/ldap.properties @@ -0,0 +1,4 @@ +urls=ldap://127.0.0.1:18880 +userDn=uid=admin,ou=system +password=secret +base=dc=jayway,dc=se diff --git a/samples/odm/src/test/resources/config/testContext.xml b/samples/odm/src/test/resources/config/testContext.xml new file mode 100644 index 00000000..7b38dc02 --- /dev/null +++ b/samples/odm/src/test/resources/config/testContext.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/odm/src/test/resources/setup_data.ldif b/samples/odm/src/test/resources/setup_data.ldif new file mode 100644 index 00000000..e6d7dce6 --- /dev/null +++ b/samples/odm/src/test/resources/setup_data.ldif @@ -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 diff --git a/settings.gradle b/settings.gradle index 1f1f8fa2..47409fdb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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-> diff --git a/src/docbkx/basic.xml b/src/docbkx/basic.xml index 005d7cc5..40d513dc 100644 --- a/src/docbkx/basic.xml +++ b/src/docbkx/basic.xml @@ -208,7 +208,7 @@ public class PersonDaoImpl implements PersonDao { public static final String BASE_DN = "dc=example,dc=com"; ... protected Name buildDn(Person p) { - return LdapNameBuilder.newInstance(BASE_DN) + return LdapNameBuilder.newLdapName(BASE_DN) .add("c", p.getCountry()) .add("ou", p.getCompany()) .add("cn", p.getFullname()) diff --git a/src/docbkx/configuration.xml b/src/docbkx/configuration.xml index 0ec4c83f..a5dd3b35 100644 --- a/src/docbkx/configuration.xml +++ b/src/docbkx/configuration.xml @@ -296,7 +296,7 @@ public class PersonService implements PersonService, BaseL } ... private LdapName getFullPersonDn(Person person) { - return LdapNameBuilder.newInstance(basePath) + return LdapNameBuilder.newLdapName(basePath) .append(person.getDn()) .build(); } diff --git a/src/docbkx/dirobjectfactory.xml b/src/docbkx/dirobjectfactory.xml index dadbe03b..c9019fee 100644 --- a/src/docbkx/dirobjectfactory.xml +++ b/src/docbkx/dirobjectfactory.xml @@ -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) diff --git a/src/docbkx/odm.xml b/src/docbkx/odm.xml index 3eb30d57..14cea152 100644 --- a/src/docbkx/odm.xml +++ b/src/docbkx/odm.xml @@ -5,172 +5,122 @@ Introduction - 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 - org.springframework.ldap.odm package (sometimes abbreviated - as o.s.l.odm). - - - - OdmManager - - The org.springframework.ldap.odm.OdmManager interface, - and its implementation, is the central class in the ODM package. The - OdmManager 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: - - - - <T> T read(Class<T> clazz, Name - dn) - - - - void create(Object entry) - - - - void update(Object entry) - - - - void delete(Object entry) - - - - <T> List<T> findAll(Class<T> clazz, Name - base, SearchControls searchControls) - - - - <T> List<T> search(Class<T> clazz, Name - base, String filter, SearchControls searchControls) - - - <T> List<T> search(Class<T> clazz, LdapQuery query) - - - - A reference to an implementation of this interface can be obtained - through the - org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean. - A basic configuration of this factory would be as follows: - - - Configuring the OdmManager Factory - -<beans> - ... - <bean id="odmManager" - class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean"> - <property name="converterManager" ref="converterManager" /> - <property name="contextSource" ref="contextSource" /> - <property name="managedClasses"> - <set> - <value>com.example.dao.SimplePerson</value> - </set> - </property> - </bean> - ... -</beans> - - - - The factory requires the list of entity classes to be managed by the - OdmManager to be explicitly declared. These classes should be - properly annotated as defined in the next section. The - converterManager referenced in the above definition is - described in . + + 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 LdapOperations + + <T> T findByDn(Name dn, Class<T> clazz) + <T> T findOne(LdapQuery query, Class<T> clazz) + <T> List<T> find(LdapQuery query, Class<T> clazz) + <T> List<T> findAll(Class<T> clazz) + <T> List<T> findAll(Name base, SearchControls searchControls, + Class<T> clazz) + <T> List<T> findAll(Name base, Filter filter, SearchControls searchControls, + Class<T> clazz) + void create(Object entry) + void update(Object entry) + void delete(Object entry) + + Annotations - Entity classes managed by the OdmManager are required - to be annotated with the annotations in the - org.springframework.ldap.odm.annotations package. The + Entity classes managed used with the object mapping methods are required + to be annotated with annotations from the + org.springframework.ldap.odm.annotations package. The available annotations are: - @Entry - Class level annotation indicating the - objectClass definitions to which the entity + @Entry - Class level annotation indicating the + objectClass definitions to which the entity maps. (required) - @Id - Indicates the entity DN; the field declaring + @Id - Indicates the entity DN; the field declaring this attribute must be a derivative of the - javax.naming.Name class. + javax.naming.Name class. (required) - @Attribute - Indicates the mapping of a directory + @Attribute - Indicates the mapping of a directory attribute to the object class field. - @Transient - Indicates the field is not persistent - and should be ignored by the OdmManager. + @Transient - Indicates the field is not persistent + and should be ignored by the OdmManager. - The @Entry and @Id attributes are - required to be declared on managed classes. @Entry 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 - @Entry annotation. + + The @Entry and @Id attributes are + required to be declared on managed classes. + @Entry 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 + @Entry annotation. For example: let's assume that you have entries in + your LDAP tree that have the objectclassesinetOrgPerson,organizationalPerson,person,top. + If you are only interested in changing the attributes defined in the person + objectclass, your @Entry annotation can be + @Entry(objectClasses = { "person", "top" }). However, if you want to manage + attributes defined in the inetOrgPerson objectclass you'll need to use the full + monty: @Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }). + - The @Id annotation is used to map the distinguished + The @Id annotation is used to map the distinguished name of the entry to a field. The field must be an instance of - javax.naming.Name or a subclass of it. + javax.naming.Name. - The @Attribute annotation is used to map object - class fields to entity fields. @Attribute is required to + The @Attribute annotation is used to map object + class fields to entity fields. @Attribute 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. @Attribute also provides the type declaration + exact matching. @Attribute 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. - The @Transient annotation is used to indicate the - field should be ignored by the OdmManager and not mapped to + The @Transient annotation is used to indicate the + field should be ignored by the object directory mapping and not mapped to an underlying LDAP property. Type Conversion - The OdmManager relies on the - org.springframework.ldap.odm.typeconversion package to - convert LDAP attributes to Java fields. The main interface in this class - is the - org.springframework.ldap.odm.typeconversion.ConverterManager. - The default ConverterManager implementation uses the + The object directory mapping relies on the + org.springframework.ldap.odm.typeconversion 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 + ObjectDirectoryMapper and its associated ConverterManager + to be explicitly configured on the LdapTemplate instance. + + The default ConverterManager implementation uses the following algorithm when parsing objects to convert fields: - Try to find and use a Converter registered for - the fromClass, syntax and - toClass and use it. + Try to find and use a Converter registered for + the fromClass, syntax and + toClass and use it. - If this fails, then if the toClass - isAssignableFrom the - fromClass then just assign it. + If this fails, then if the toClass + isAssignableFrom the + fromClass then just assign it. If this fails try to find and use a - Converter registered for the - fromClass and the toClass ignoring the + Converter registered for the + fromClass and the toClass ignoring the syntax. @@ -252,6 +202,17 @@ </set> </property> </bean> + +<bean id="ldapTemplate" + class="org.springframework.ldap.core.LdapTemplate"> + <property name="objectDirectoryMapper"> + <bean class="org.springframework.ldap.odm.impl.DefaultObjectDirectoryMapper"> + <property name="converterManager" ref="converterManager" /> + </bean> + </property> + <!-- More configuration of LdapTemplate here --> +</bean> + @@ -259,31 +220,47 @@ Execution - After all components are configured, directory interaction can be - achieved through a reference to the OdmManager, as shown in - this example: - + + When all components have been properly configured and annotated, the object mapping + methods of LdapTemplate can be used as follows: Execution -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<SimplePerson> 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>Person< findAll() { + return ldapTemplate.findAll(Person.class); + } + + public List>Person< findByLastName(String lastName) { + return ldapTemplate.find(query().where("sn").is(lastName), Person.class); } } diff --git a/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java new file mode 100644 index 00000000..7f9bce24 --- /dev/null +++ b/test/integration-tests/src/main/java/org/springframework/ldap/itest/odm/Person.java @@ -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 desc; + + @Attribute(name = "uid") + private List 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 getDesc() { + return desc; + } + + public void setDesc(List desc) { + this.desc = desc; + } + + public List getUserId() { + return userId; + } + + public void setUserId(List userId) { + this.userId = userId; + } + + public String getTelephoneNumber() { + return telephoneNumber; + } + + public void setTelephoneNumber(String telephoneNumber) { + this.telephoneNumber = telephoneNumber; + } +} diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmITest.java new file mode 100644 index 00000000..c8283ef7 --- /dev/null +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/odm/LdapTemplateOdmITest.java @@ -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 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 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 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); + } + } +}