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 extends Attribute> attributesEnumeration = attributes.getAll();
+ // Loop through all of the JNDI attributes
+ while (attributesEnumeration.hasMoreElements()) {
+ Attribute currentAttribute = (Attribute)attributesEnumeration.nextElement();
+ // Add the current attribute to the map keyed on the lowercased (case indep) id of the attribute
+ attributeValueMap.put(new CaseIgnoreString(currentAttribute.getID()), currentAttribute);
+ }
+
+ // Now loop through all the fields in the Java representation populating it with values from the
+ // attributeValueMap
+ for (Field field : metaData) {
+ // Get the current field
+ AttributeMetaData attributeInfo = metaData.getAttribute(field);
+ // We deal with the Id field specially
+ if (!attributeInfo.isId()) {
+ // Not the ID - but is is multi valued?
+ if (!attributeInfo.isList()) {
+ // No - its single valued, grab the JNDI attribute that corresponds to the metadata on the
+ // current field
+ Attribute attribute = attributeValueMap.get(attributeInfo.getName());
+ // There is no guarantee that this attribute is present in the directory - so ignore nulls
+ if (attribute != null) {
+ // Grab the JNDI value
+ Object value = attribute.get();
+ // Check the value is not null
+ if (value != null) {
+ // Convert the JNDI value to its Java representation - this will throw if the
+ // conversion fails
+ Object convertedValue = converterManager.convert(value, attributeInfo.getSyntax(),
+ attributeInfo.getValueClass());
+ // Set it in the Java version
+ field.set(result, convertedValue);
+ }
+ }
+ } else { // We are dealing with a multi valued attribute
+ // We need to build up a list of values
+ List