diff --git a/repository/org.testng/testng/5.0/testng-5.0.jar b/repository/org.testng/testng/5.0/testng-5.0.jar
new file mode 100644
index 00000000..fa73e349
Binary files /dev/null and b/repository/org.testng/testng/5.0/testng-5.0.jar differ
diff --git a/spring-ldap-odm/build-original.xml b/spring-ldap-odm/build-original.xml
new file mode 100644
index 00000000..5c8174ca
--- /dev/null
+++ b/spring-ldap-odm/build-original.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-ldap-odm/build.xml b/spring-ldap-odm/build.xml
new file mode 100644
index 00000000..280ce466
--- /dev/null
+++ b/spring-ldap-odm/build.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Unit test report has been generated.
+The HTML report is ${target.testresults.html.dir}/index.html
+
+
+
+
+
diff --git a/spring-ldap-odm/ivy.xml b/spring-ldap-odm/ivy.xml
new file mode 100644
index 00000000..32b86cbe
--- /dev/null
+++ b/spring-ldap-odm/ivy.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-ldap-odm/project.properties b/spring-ldap-odm/project.properties
new file mode 100644
index 00000000..2c371cea
--- /dev/null
+++ b/spring-ldap-odm/project.properties
@@ -0,0 +1,14 @@
+# properties defined in this file are overridable by a local build.properties in this project dir
+
+# The location of the common build system
+common.build.dir=${basedir}/../common-build
+
+project.base.version=1.2-dev
+#project.version=${project.base.version}
+#ivy.status=release
+
+javac.source=1.5
+javac.target=1.5
+
+# extend common-build with source capability
+main.build.configs=global,buildtime,test,source
diff --git a/spring-ldap-odm/src/itest/java/conf/beans.xml b/spring-ldap-odm/src/itest/java/conf/beans.xml
new file mode 100644
index 00000000..8f84cd19
--- /dev/null
+++ b/spring-ldap-odm/src/itest/java/conf/beans.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/contextmapping/AnnotatedClassContextMapperTest.java b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/contextmapping/AnnotatedClassContextMapperTest.java
new file mode 100644
index 00000000..16932b56
--- /dev/null
+++ b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/contextmapping/AnnotatedClassContextMapperTest.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.contextmapping;
+
+import java.util.Date;
+import java.util.Map;
+
+import javax.naming.Name;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.odm.annotations.DirAttribute;
+import org.springframework.ldap.odm.annotations.NamingAttribute;
+import org.springframework.ldap.odm.annotations.NamingSuffix;
+import org.springframework.ldap.odm.annotations.ObjectClasses;
+import org.springframework.ldap.odm.attributetypes.LdapTypeConverter;
+import org.springframework.ldap.odm.attributetypes.ReferencedEntryEditorFactory;
+import org.springframework.ldap.odm.contextmapping.exception.ContextMapperException;
+import org.springframework.ldap.odm.entity.MockEntity;
+import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class AnnotatedClassContextMapperTest extends
+ AbstractDependencyInjectionSpringContextTests {
+ private static final Log LOGGER = LogFactory
+ .getLog(AnnotatedClassContextMapperTest.class);
+
+ private LdapTypeConverter typeConverter;
+
+ private ReferencedEntryEditorFactory refEditorFactory;
+
+ public void setRefEditorFactory(
+ ReferencedEntryEditorFactory refEditorFactory) {
+ this.refEditorFactory = refEditorFactory;
+ }
+
+ public void setTypeConverter(LdapTypeConverter typeConverter) {
+ this.typeConverter = typeConverter;
+ }
+
+ @Override
+ protected String[] getConfigLocations() {
+ return new String[] { "conf/beans.xml" };
+ }
+
+ public void testMapsDirectoryAttributesToBeanProperties() {
+ try {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockEntity.class, typeConverter, refEditorFactory);
+ ContextMap contextMap = contextMapper.getContextMap();
+ Assert.assertEquals("cn", contextMap.attributeNameFor("Name"),
+ "Name mapping not as expected");
+ Assert.assertEquals("mail", contextMap.attributeNameFor("Mail"),
+ "Mail mapping not as expected");
+ Assert.assertEquals("addr", contextMap.attributeNameFor("Address"),
+ "Address mapping not as expected");
+ Assert.assertEquals("userpassword", contextMap
+ .attributeNameFor("Password"),
+ "Password mapping not as expected");
+ Assert.assertEquals("desc", contextMap
+ .attributeNameFor("Description"),
+ "Description mapping not as expected");
+ } catch (ContextMapperException e) {
+ LOGGER.debug(e.getMessage());
+ Assert.fail(e.getMessage(), e);
+ }
+ }
+
+ public void testReturnsCorrectNamingAttribute() {
+ try {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockEntity.class, typeConverter, refEditorFactory);
+ Assert.assertEquals("cn", contextMapper.getNamingAttribute(),
+ "Context mapper naming attribute not as expected");
+
+ } catch (ContextMapperException e) {
+ LOGGER.debug(e.getMessage());
+ Assert.fail(e.getMessage(), e);
+ }
+ }
+
+ public void testReturnsCorrectObjectClasses() {
+ try {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockEntity.class, typeConverter, refEditorFactory);
+ Assert.assertEquals(new String[] { "top", "person",
+ "organizationalPerson", "inetorgperson" }, contextMapper
+ .getObjectClasses(),
+ "Context mapper naming attribute not as expected");
+
+ } catch (ContextMapperException e) {
+ LOGGER.debug(e.getMessage());
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ public void testBuildDn() {
+ try {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockEntity.class, typeConverter, refEditorFactory);
+ MockEntity entity = new MockEntity();
+ entity.setName("Mr Bean");
+ entity.setAddress("Somewhere in england");
+ entity.setDescription(new String[] { "The quick brown fox jumped",
+ "over the lazy dog" });
+ Name dn = contextMapper.buildDn(entity);
+ Assert.assertEquals("cn=Mr Bean, ou=people, dc=example, dc=com", dn
+ .toString(), "dn not as expected");
+
+ } catch (ContextMapperException e) {
+ LOGGER.debug(e.getMessage());
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ public void testMapToContext() {
+ try {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockEntity.class, typeConverter, refEditorFactory);
+ MockEntity entity = new MockEntity();
+ entity.setName("Mr Bean");
+ entity.setMail("bean@bean.com");
+ entity.setAddress("Somewhere in england");
+ entity.setPassword("fred".getBytes());
+ entity.setDescription(new String[] { "The quick brown fox jumped",
+ "over the lazy dog" });
+ entity.setAcceptEmails(false);
+ entity.setResetLogin(new Date(1L));
+ entity.setCreator(new DistinguishedName(
+ "uid=amAdmin,ou=people,dc=myretsu,dc=com"));
+ entity.setFailedLogins(3);
+ DirContextAdapter ctxAdapter = new DirContextAdapter();
+ contextMapper.mapToContext(entity, ctxAdapter);
+
+ Assert.assertEquals("Mr Bean", ctxAdapter.getStringAttribute("cn"),
+ "Name mapping not as expected");
+ Assert
+ .assertEquals("bean@bean.com", ctxAdapter
+ .getStringAttribute("mail"),
+ "Mail mapping not as expected");
+ Assert.assertEquals("Somewhere in england", ctxAdapter
+ .getObjectAttribute("addr"),
+ "Address mapping not as expected");
+ Assert.assertEquals("fred", new String((byte[]) ctxAdapter
+ .getObjectAttribute("userpassword")),
+ "Password mapping not as expected");
+ Assert.assertEquals("The quick brown fox jumped", ctxAdapter
+ .getStringAttributes("desc")[0],
+ "Description mapping not as expected");
+ Assert.assertEquals("false", ctxAdapter
+ .getStringAttribute("acceptemails"),
+ "AcceptEmails mapping not as expected");
+ Assert.assertEquals("19700101100000.1", ctxAdapter
+ .getStringAttribute("loginresettime"),
+ "ResetLogin mapping not as expected");
+ Assert.assertEquals("3", ctxAdapter
+ .getStringAttribute("failedlogins"),
+ "FailedLogins mapping not as expected");
+ Assert.assertEquals("uid=amAdmin, ou=people, dc=myretsu, dc=com",
+ ctxAdapter.getStringAttribute("creatorname"),
+ "Creator mapping not as expected");
+ } catch (ContextMapperException e) {
+ LOGGER.debug(e.getMessage());
+ Assert.fail(e.getMessage(), e);
+ }
+ }
+
+ public void testMapFromContext() throws ContextMapperException {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockEntity.class, typeConverter, refEditorFactory);
+ DirContextAdapter ctxAdapter = new DirContextAdapter();
+ ctxAdapter.setAttributeValue("cn", "Mr Bean");
+ ctxAdapter.setAttributeValue("mail", "bean@bean.com");
+ ctxAdapter.setAttributeValue("addr", "Somewhere in england");
+ ctxAdapter.setAttributeValue("userpassword", "fred".getBytes());
+ ctxAdapter
+ .setAttributeValues("desc", new String[] { "elem1", "elem2" });
+ ctxAdapter.setAttributeValue("userpassword", "fred".getBytes());
+ ctxAdapter.setAttributeValue("acceptemails", "true");
+ ctxAdapter.setAttributeValue("loginresettime", "19700101100000.1");
+ ctxAdapter.setAttributeValue("failedlogins", "3");
+ ctxAdapter.setAttributeValue("creatorname",
+ "uid=amAdmin,ou=people,dc=myretsu,dc=com");
+
+ MockEntity entity = (MockEntity) contextMapper
+ .mapFromContext(ctxAdapter);
+ Assert.assertEquals(entity.getMail(), "bean@bean.com",
+ "The mocked entity should match what has been mapped");
+ Assert.assertEquals("Mr Bean", entity.getName(),
+ "Name mapping not as expected");
+ Assert.assertEquals(entity.getMail(), "bean@bean.com",
+ "Mail mapping not as expected");
+ Assert.assertEquals(entity.getAddress(), "Somewhere in england",
+ "Address mapping not as expected");
+ Assert.assertEquals("fred", new String(entity.getPassword()),
+ "Password mapping not as expected");
+ Assert.assertEquals("elem1", entity.getDescription()[0],
+ "Description mapping not as expected");
+ Assert.assertEquals(Boolean.TRUE, entity.getAcceptEmails(),
+ "AcceptEmails mapping not as expected");
+ Assert.assertEquals(new Date(1L), entity.getResetLogin(),
+ "ResetLogin mapping not as expected");
+ Assert.assertEquals(new Integer(3), entity.getFailedLogins(),
+ "FailedLogin mapping not as expected");
+ Assert.assertEquals(new DistinguishedName(
+ "uid=amAdmin,ou=people,dc=myretsu,dc=com"),
+ entity.getCreator(), "Creator mapping not as expected");
+
+ }
+
+ @Test(groups = { "AnnotatedClassContextMapper" }, expectedExceptions = ContextMapperException.class)
+ public void throwsExceptionWhenNamingSuffixHasSyntaxError()
+ throws ContextMapperException {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockModelNamingSuffixSyntaxError.class, typeConverter,
+ refEditorFactory);
+ Assert
+ .fail("AnnotatedClassContextMapper should throw an exception when naming path annotation syntax is "
+ + "incorrect.");
+ }
+
+ @Test(groups = { "AnnotatedClassContextMapper" }, expectedExceptions = ContextMapperException.class)
+ public void throwsExceptionWhenNamingSuffixNotInSeparateElements()
+ throws ContextMapperException {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockModelNamingSuffixNotInSeparateElements.class,
+ typeConverter, refEditorFactory);
+ Assert
+ .fail("AnnotatedClassContextMapper should throw an exception when naming path annotation syntax is "
+ + "incorrect.");
+ }
+
+ @Test(groups = { "AnnotatedClassContextMapper" }, expectedExceptions = ContextMapperException.class)
+ public void throwsExceptionWhenModelObjectMissingGetterOrSetter()
+ throws ContextMapperException {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockModelMissingSetter.class, typeConverter, refEditorFactory);
+ Assert.fail("Should've thrown exception due to missing getter/setter");
+ }
+
+ @Test(groups = { "AnnotatedClassContextMapper" })
+ public void throwsExceptionWhenBeanPropertyNotAValidTranslationType() {
+ try {
+ AnnotatedClassContextMapper contextMapper = new AnnotatedClassContextMapper(
+ MockModelInvalidTranslationType.class, typeConverter,
+ refEditorFactory);
+ Assert
+ .fail("Should've thrown exception due to missing getter/setter");
+ } catch (ContextMapperException e) {
+ Assert
+ .assertTrue(e
+ .getMessage()
+ .contains(
+ "MockModelInvalidTranslationType.getMap() has invalid return type"));
+ }
+
+ }
+
+ @NamingAttribute("cn")
+ @NamingSuffix( { "ou=people", "dc=example", "dc=com" })
+ @ObjectClasses( { "top", "person", "organizationalPerson", "inetorgperson" })
+ public class MockModelMissingSetter {
+ @DirAttribute("cn")
+ private String name;
+
+ @DirAttribute("addr")
+ private String address;
+
+ @DirAttribute("mail")
+ private String mail;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public String getMail() {
+ return mail;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+ }
+
+ @NamingAttribute("cn")
+ @NamingSuffix( { "ou=people", "dc=example", "dc=com" })
+ @ObjectClasses( { "top", "person", "organizationalPerson", "inetorgperson" })
+ public class MockModelBadMethodName {
+
+ @DirAttribute("cn")
+ private String name;
+
+ @DirAttribute("addr")
+ private String address;
+
+ public String zzzName() {
+ return name;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+ }
+
+ @NamingAttribute("cn")
+ @NamingSuffix( { "ou=people,dc=example ,dc=com" })
+ @ObjectClasses( { "top", "person", "organizationalPerson", "inetorgperson" })
+ public class MockModelNamingSuffixNotInSeparateElements {
+ }
+
+ @NamingAttribute("cn")
+ @NamingSuffix( { "ou=people", "dc 0);
+ }
+
+ @Test(groups = "LdapDao")
+ public void filterByBeanProperty()
+ {
+ ldapDao.create(testPerson);
+ List results = ldapDao.filterByBeanProperty(testPerson.getEmailAddress(), "EmailAddress", TestPerson.class);
+ LOGGER.debug("filterByBeanProperty returned: " + results.size() + " results.");
+ Assert.assertTrue(results.size() > 0, "Filter should return collection containing more than one result. ");
+ }
+
+
+ @Test(groups = "LdapDao")
+ public void updateEntity()
+ {
+ try
+ {
+ ldapDao.create(testPerson);
+ String newEmail = "new@email.com";
+ testPerson.setEmailAddress(newEmail);
+ ldapDao.update(testPerson);
+ TestPerson retrieved = (TestPerson) ldapDao.findByNamingAttribute(testPerson.getIdentifier(),
+ TestPerson.class);
+ Assert.assertEquals(retrieved.getEmailAddress(), newEmail, "Email name not updated.");
+ }
+ catch (Exception unexpected)
+ {
+ Assert.fail("Unable to update the details of an existing user: " + unexpected.getMessage());
+ }
+ }
+
+
+ @Test(groups = "LdapDao")
+ public void updateEntityThrowsExceptionWhenNameNotBound()
+ {
+ try
+ {
+ String newEmail = "new@email.com";
+ testPerson.setEmailAddress(newEmail);
+ ldapDao.update(testPerson);
+ Assert.fail("Should've failed since the entry doesn't exist");
+ }
+ catch (EntryNotFoundException e)
+ {
+ Assert.assertTrue(e instanceof EntryNotFoundException);
+ Assert.assertTrue(e.getMessage().contains("Entry not found"), "Error message not as expected.");
+ }
+ }
+
+ @Test(groups = "LdapDao")
+ public void deleteEntity()
+ {
+ ldapDao.create(testPerson);
+ LOGGER.debug(testPerson);
+ ldapDao.delete(testPerson);
+ TestPerson retrieved = (TestPerson) ldapDao.findByNamingAttribute(testPerson.getIdentifier(), TestPerson.class);
+ Assert.assertNull(retrieved, "Entity should've been deleted.");
+ }
+
+
+}
diff --git a/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/MockEntity.java b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/MockEntity.java
new file mode 100644
index 00000000..968b7039
--- /dev/null
+++ b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/MockEntity.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.entity;
+
+import java.util.Date;
+
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.odm.annotations.DirAttribute;
+import org.springframework.ldap.odm.annotations.NamingAttribute;
+import org.springframework.ldap.odm.annotations.NamingSuffix;
+import org.springframework.ldap.odm.annotations.ObjectClasses;
+
+@NamingAttribute("cn")
+@NamingSuffix({"ou = people", "dc = example", "dc=com"})
+@ObjectClasses({"top", "person", "organizationalPerson", "inetorgperson"})
+public class MockEntity
+{
+
+ @DirAttribute("cn")
+ private String name;
+
+ @DirAttribute("addr")
+ private String address;
+
+ @DirAttribute("mail")
+ private String mail;
+
+ @DirAttribute("userpassword")
+ private byte[] password;
+
+ @DirAttribute("desc")
+ private String[] description;
+
+ @DirAttribute("acceptemails")
+ private Boolean acceptEmails;
+
+ @DirAttribute("loginresettime")
+ private Date resetLogin;
+
+ @DirAttribute("failedlogins")
+ private Integer failedLogins;
+
+ @DirAttribute("creatorname")
+ private DistinguishedName creator;
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public String getAddress()
+ {
+ return address;
+ }
+
+ public void setAddress(String address)
+ {
+ this.address = address;
+ }
+
+ public String getMail()
+ {
+ return mail;
+ }
+
+ public void setMail(String mail)
+ {
+ this.mail = mail;
+ }
+
+ public byte[] getPassword()
+ {
+ return password;
+ }
+
+ public void setPassword(byte[] password)
+ {
+ this.password = password;
+ }
+
+ public String[] getDescription()
+ {
+ return description;
+ }
+
+ public void setDescription(String[] description)
+ {
+ this.description = description;
+ }
+
+ public Boolean getAcceptEmails()
+ {
+ return acceptEmails;
+ }
+
+ public void setAcceptEmails(Boolean acceptEmails)
+ {
+ this.acceptEmails = acceptEmails;
+ }
+
+ public Date getResetLogin()
+ {
+ return resetLogin;
+ }
+
+ public void setResetLogin(Date resetLogin)
+ {
+ this.resetLogin = resetLogin;
+ }
+
+ public Integer getFailedLogins()
+ {
+ return failedLogins;
+ }
+
+ public void setFailedLogins(Integer failedLogins)
+ {
+ this.failedLogins = failedLogins;
+ }
+
+ public DistinguishedName getCreator()
+ {
+ return creator;
+ }
+
+ public void setCreator(DistinguishedName creator)
+ {
+ this.creator = creator;
+ }
+}
diff --git a/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/TestPerson.java b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/TestPerson.java
new file mode 100644
index 00000000..bdc066ae
--- /dev/null
+++ b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/TestPerson.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.entity;
+
+import org.springframework.ldap.odm.annotations.DirAttribute;
+import org.springframework.ldap.odm.annotations.NamingAttribute;
+import org.springframework.ldap.odm.annotations.NamingSuffix;
+import org.springframework.ldap.odm.annotations.ObjectClasses;
+
+import com.thoughtworks.xstream.XStream;
+
+
+@NamingAttribute("uid")
+@NamingSuffix({"ou=people"})
+@ObjectClasses({"top", "person", "organizationalPerson", "inetorgperson"})
+public class TestPerson
+{
+ /*********************************** Directory mapped fields ************************************/
+ @DirAttribute("uid")
+ private String identifier;
+
+ @DirAttribute("sn")
+ private String lastName;
+
+ @DirAttribute("givenName")
+ private String givenName;
+
+ @DirAttribute("cn")
+ private String fullName;
+
+ @DirAttribute("mail")
+ private String emailAddress;
+
+ @DirAttribute("userpassword")
+ private byte[] password;
+
+ /************************************************************************************************/
+
+ public String getIdentifier()
+ {
+ return identifier;
+ }
+
+ public void setIdentifier(String identifier)
+ {
+ this.identifier = identifier;
+ }
+
+ public String getLastName()
+ {
+ return lastName;
+ }
+
+ public void setLastName(String lastName)
+ {
+ this.lastName = lastName;
+ }
+
+ public String getGivenName()
+ {
+ return givenName;
+ }
+
+ public void setGivenName(String givenName)
+ {
+ this.givenName = givenName;
+ }
+
+ public String getFullName()
+ {
+ return fullName;
+ }
+
+ public void setFullName(String fullName)
+ {
+ this.fullName = fullName;
+ }
+
+ public String getEmailAddress()
+ {
+ return emailAddress;
+ }
+
+ public void setEmailAddress(String emailAddress)
+ {
+ this.emailAddress = emailAddress;
+ }
+
+ public byte[] getPassword()
+ {
+ return password;
+ }
+
+ public void setPassword(byte[] password)
+ {
+ this.password = password;
+ }
+
+ public String toString()
+ {
+ XStream xStream = new XStream();
+ xStream.alias("TestPerson", TestPerson.class);
+ return "\n" + xStream.toXML(this);
+ }
+
+
+}
diff --git a/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/TestRole.java b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/TestRole.java
new file mode 100644
index 00000000..be35d4fe
--- /dev/null
+++ b/spring-ldap-odm/src/itest/java/org/springframework/ldap/odm/entity/TestRole.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.entity;
+
+import org.springframework.ldap.odm.annotations.DirAttribute;
+import org.springframework.ldap.odm.annotations.NamingAttribute;
+import org.springframework.ldap.odm.annotations.NamingSuffix;
+import org.springframework.ldap.odm.annotations.ObjectClasses;
+
+import com.thoughtworks.xstream.XStream;
+
+@NamingAttribute("cn")
+@NamingSuffix({"ou=roles"})
+@ObjectClasses({"top", "organizationalRole"})
+
+public class TestRole
+{
+ /*********************************** Directory mapped fields ************************************/
+ @DirAttribute("cn")
+ private String roleName;
+
+ @DirAttribute
+ private String description;
+
+ @DirAttribute("roleOccupant")
+ private TestPerson[] members;
+
+ /************************************************************************************************/
+
+ public String getRoleName()
+ {
+ return roleName;
+ }
+
+ public void setRoleName(String roleName)
+ {
+ this.roleName = roleName;
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ public void setDescription(String description)
+ {
+ this.description = description;
+ }
+
+ public TestPerson[] getMembers()
+ {
+ return members;
+ }
+
+ public void setMembers(TestPerson[] members)
+ {
+ this.members = members;
+ }
+
+ public String toString()
+ {
+ XStream xStream = new XStream();
+ xStream.alias("TestRole", TestRole.class);
+ xStream.alias("TestPerson", TestPerson.class);
+ return "\n" + xStream.toXML(this);
+ }
+
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/DirAttribute.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/DirAttribute.java
new file mode 100644
index 00000000..c046f617
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/DirAttribute.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.annotations;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.FIELD;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+@Documented
+@Target({FIELD})
+@Retention(RUNTIME)
+public @interface DirAttribute
+{
+ String value() default "";
+}
+
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/NamingAttribute.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/NamingAttribute.java
new file mode 100644
index 00000000..fed1545f
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/NamingAttribute.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.annotations;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.TYPE;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+
+@Documented
+@Target({TYPE})
+@Retention(RUNTIME)
+public @interface NamingAttribute
+{
+ String value();
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/NamingSuffix.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/NamingSuffix.java
new file mode 100644
index 00000000..47b601c6
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/NamingSuffix.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.annotations;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.TYPE;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+/*
+ * Copyright 2006 by Sensis. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+
+@Documented
+@Target({TYPE})
+@Retention(RUNTIME)
+public @interface NamingSuffix
+{
+ String[] value();
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/ObjectClasses.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/ObjectClasses.java
new file mode 100644
index 00000000..ea38aaee
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/annotations/ObjectClasses.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.annotations;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.TYPE;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+@Documented
+@Target({TYPE})
+@Retention(RUNTIME)
+public @interface ObjectClasses
+{
+ String[] value();
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/LdapTypeConverter.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/LdapTypeConverter.java
new file mode 100644
index 00000000..4d9228e7
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/LdapTypeConverter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.attributetypes;
+
+import java.beans.PropertyEditor;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.naming.ldap.LdapName;
+
+import org.springframework.beans.SimpleTypeConverter;
+import org.springframework.beans.propertyeditors.CustomDateEditor;
+import org.springframework.ldap.core.DistinguishedName;
+
+public class LdapTypeConverter extends SimpleTypeConverter
+{
+
+ public LdapTypeConverter()
+ {
+ super();
+ registerCustomEditor(Date.class,
+ new CustomDateEditor(new SimpleDateFormat("yyyyMMddHHmmss.S"), true));
+ registerCustomEditor(LdapName.class, new NameEditor(LdapName.class));
+ registerCustomEditor(DistinguishedName.class, new NameEditor(DistinguishedName.class));
+ }
+
+ public String getAsText(Object value)
+ {
+ if (value instanceof String)
+ {
+ return (String) value;
+ }
+ PropertyEditor pe = propertyEditorFor(value.getClass());
+ if (pe != null)
+ {
+ pe.setValue(value);
+ return pe.getAsText();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public String[] getAllAsText(Object[] values)
+ {
+ String[] textValues = new String[values.length];
+ for (int i = 0; i < values.length; i++)
+ {
+ textValues[i] = getAsText(values[i]);
+ }
+ return textValues;
+ }
+
+ private PropertyEditor propertyEditorFor(Class clazz)
+ {
+ PropertyEditor pe = findCustomEditor(clazz, null);
+ if (pe != null)
+ {
+ return pe;
+ }
+ else
+ {
+ pe = getDefaultEditor(clazz);
+ return pe;
+ }
+ }
+
+
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/NameEditor.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/NameEditor.java
new file mode 100644
index 00000000..ae17ecdb
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/NameEditor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.attributetypes;
+
+import java.beans.PropertyEditorSupport;
+
+import javax.naming.ldap.LdapName;
+
+import org.springframework.ldap.core.DistinguishedName;
+
+public class NameEditor extends PropertyEditorSupport
+{
+ private Class conversionClass;
+
+ public NameEditor(Class conversionClass)
+ {
+ this.conversionClass = conversionClass;
+ if (!conversionClass.equals(DistinguishedName.class) && !conversionClass.equals(LdapName.class))
+ {
+ throw new RuntimeException("NameEditor can only be created for LdapName or DistinguishedName");
+ }
+ }
+
+ public String getAsText()
+ {
+ Object value = conversionClass.cast(getValue());
+ return value != null ? value.toString() : "";
+ }
+
+ public void setAsText(String text) throws IllegalArgumentException
+ {
+ try
+ {
+ setValue(conversionClass.getConstructor(String.class).newInstance(text));
+ }
+ catch (Exception e)
+ {
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ReferencedEntryEditor.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ReferencedEntryEditor.java
new file mode 100644
index 00000000..30996977
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ReferencedEntryEditor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.attributetypes;
+
+import java.beans.PropertyEditorSupport;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.odm.contextmapping.AnnotatedClassContextMapper;
+import org.springframework.ldap.odm.contextmapping.exception.ContextMapperException;
+
+public class ReferencedEntryEditor extends PropertyEditorSupport
+{
+ private LdapTemplate ldapTemplate;
+ private AnnotatedClassContextMapper contextMapper;
+
+ public ReferencedEntryEditor(LdapTemplate ldapTemplate, AnnotatedClassContextMapper contextMapper)
+ {
+ this.ldapTemplate = ldapTemplate;
+ this.contextMapper = contextMapper;
+ }
+
+ public String getAsText()
+ {
+ try
+ {
+ return contextMapper.buildDn(getValue()).toString();
+ }
+ catch (ContextMapperException e)
+ {
+ throw new RuntimeException("No context mapper for class: " + getValue().getClass().getSimpleName());
+ }
+ }
+
+ public void setAsText(String text) throws IllegalArgumentException
+ {
+ try
+ {
+ setValue(ldapTemplate.lookup(new LdapName(text), contextMapper));
+ }
+ catch (InvalidNameException e)
+ {
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ReferencedEntryEditorFactory.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ReferencedEntryEditorFactory.java
new file mode 100644
index 00000000..50c3408a
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ReferencedEntryEditorFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.attributetypes;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.odm.attributetypes.exception.ReferencedEntryEditorCreationException;
+import org.springframework.ldap.odm.contextmapping.AnnotatedClassContextMapper;
+import org.springframework.ldap.odm.contextmapping.ContextMapperFactory;
+import org.springframework.ldap.odm.contextmapping.exception.ContextMapperException;
+
+public class ReferencedEntryEditorFactory
+{
+ private static final Log LOGGER = LogFactory.getLog(ReferencedEntryEditorFactory.class);
+ private ContextMapperFactory contextMapperFactory;
+ private LdapTemplate ldapTemplate;
+ private Map referencedEntryEditors;
+
+ public ReferencedEntryEditorFactory(
+ LdapTemplate ldapTemplate)
+ {
+ this.ldapTemplate = ldapTemplate;
+ this.referencedEntryEditors = new HashMap();
+ }
+
+ public ReferencedEntryEditor referencedEntryEditorForClass(Class clazz)
+ throws ReferencedEntryEditorCreationException
+ {
+ if (referencedEntryEditors.containsKey(clazz))
+ {
+ LOGGER.debug("Returning cached referenced entry editor for class: " + clazz.getSimpleName());
+ return referencedEntryEditors.get(clazz);
+ }
+ else
+ {
+ LOGGER.debug("Attempting to create a referenced entry editor for class: "
+ + clazz.getSimpleName());
+ AnnotatedClassContextMapper contextMapper = null;
+ try
+ {
+ contextMapper = contextMapperFactory.contextMapperForClass(clazz);
+ ReferencedEntryEditor referencedEntryEditor =
+ new ReferencedEntryEditor(ldapTemplate, contextMapper);
+ referencedEntryEditors.put(clazz, referencedEntryEditor);
+ return referencedEntryEditor;
+ }
+ catch (ContextMapperException e)
+ {
+ throw new ReferencedEntryEditorCreationException(e.getMessage(), e);
+ }
+ }
+ }
+
+ public void setContextMapperFactory(ContextMapperFactory contextMapperFactory)
+ {
+ this.contextMapperFactory = contextMapperFactory;
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ValidConversionType.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ValidConversionType.java
new file mode 100644
index 00000000..3b835892
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/ValidConversionType.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.attributetypes;
+
+import java.util.Date;
+
+import javax.naming.ldap.LdapName;
+
+import org.springframework.ldap.core.DistinguishedName;
+
+/* Should be able to support any of Spring's conversion types. Add a test case if you add one */
+public enum ValidConversionType
+{
+ BYTE_ARRAY(byte[].class),
+ BOOLEAN(Boolean.class),
+ STRING(String.class),
+ STRING_ARRAY(String[].class),
+ DATE(Date.class),
+ DATE_ARRAY(Date[].class),
+ LDAP_NAME(LdapName.class),
+ LDAP_NAME_ARRAY(LdapName[].class),
+ DISTINGUISHED_NAME(DistinguishedName.class),
+ DISTINGUISHED_NAME_ARRAY(DistinguishedName[].class),
+ INTEGER(Integer.class),
+ INTEGER_ARRAY(Integer[].class),
+ LONG(Long.class),
+ LONG_ARRAY(Long[].class);
+
+ private final Class clazz;
+
+
+ ValidConversionType(Class validType)
+ {
+ this.clazz = validType;
+
+ }
+
+ public Class getClazz()
+ {
+ return clazz;
+ }
+
+ public static String listTypes()
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < ValidConversionType.values().length; i++)
+ {
+ ValidConversionType validType = ValidConversionType.values()[i];
+ sb.append("\n");
+ sb.append(validType.getClazz().getSimpleName());
+ if (i != ValidConversionType.values().length - 1)
+ {
+ sb.append(",");
+ }
+ }
+ return sb.toString();
+ }
+
+ public static boolean isValidConversionType(Class returnType)
+ {
+ for (ValidConversionType type : ValidConversionType.values())
+ {
+ if (returnType.equals(type.getClazz()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
+
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/exception/ReferencedEntryEditorCreationException.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/exception/ReferencedEntryEditorCreationException.java
new file mode 100644
index 00000000..8530bb14
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/attributetypes/exception/ReferencedEntryEditorCreationException.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.attributetypes.exception;
+
+public class ReferencedEntryEditorCreationException extends Exception
+{
+ public ReferencedEntryEditorCreationException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/AnnotatedClassContextMapper.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/AnnotatedClassContextMapper.java
new file mode 100644
index 00000000..6bd84457
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/AnnotatedClassContextMapper.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.contextmapping;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.naming.Name;
+import javax.naming.directory.Attribute;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.ldap.core.ContextAssembler;
+import org.springframework.ldap.core.ContextMapper;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.odm.annotations.DirAttribute;
+import org.springframework.ldap.odm.annotations.NamingAttribute;
+import org.springframework.ldap.odm.annotations.NamingSuffix;
+import org.springframework.ldap.odm.annotations.ObjectClasses;
+import org.springframework.ldap.odm.attributetypes.LdapTypeConverter;
+import org.springframework.ldap.odm.attributetypes.ReferencedEntryEditorFactory;
+import org.springframework.ldap.odm.attributetypes.ValidConversionType;
+import org.springframework.ldap.odm.attributetypes.exception.ReferencedEntryEditorCreationException;
+import org.springframework.ldap.odm.contextmapping.exception.ContextMapperException;
+import org.springframework.util.StringUtils;
+
+public class AnnotatedClassContextMapper implements ContextMapper, ContextAssembler
+{
+ private static final Log LOGGER = LogFactory.getLog(AnnotatedClassContextMapper.class);
+
+ private LdapTypeConverter typeConverter;
+ private ReferencedEntryEditorFactory refEditorFactory;
+
+ private Class> clazz;
+ private String namingAttribute;
+ private String[] objectClasses;
+ private String[] namingSuffixElements;
+ private DistinguishedName namingSuffix;
+
+ private ContextMap contextMap;
+ private Map propertyGetters;
+ private Map propertySetters;
+
+
+ public AnnotatedClassContextMapper(Class> clazz, LdapTypeConverter typeConverter,
+ ReferencedEntryEditorFactory refEditorFactory) throws ContextMapperException
+ {
+ this.clazz = clazz;
+ this.typeConverter = typeConverter;
+ this.refEditorFactory = refEditorFactory;
+ checkConfigured();
+ checkRequiredMetaData();
+ buildNamingSuffix();
+ mapAttributesToBeanProperties();
+ cacheGettersAndSetters();
+ registerReferencedEntries();
+ }
+
+ private void checkConfigured() throws ContextMapperException
+ {
+ if (clazz == null)
+ {
+ throw new ContextMapperException("Error creating context mapper."
+ + ". Class> arugment is null");
+ }
+
+ if (this.typeConverter == null)
+ {
+ throw new ContextMapperException("Error creating context mapper for class: "
+ + clazz.getSimpleName()
+ + ". AttributeTypeTranslator arugment is null");
+ }
+
+ if (this.refEditorFactory == null)
+ {
+ throw new ContextMapperException("Error creating context mapper for class: "
+ + clazz.getSimpleName()
+ + ". ReferencedEntryEditorFactory arugment is null");
+ }
+ }
+
+ private void checkRequiredMetaData() throws ContextMapperException
+ {
+ NamingAttribute attnNamingAttr = clazz.getAnnotation(NamingAttribute.class);
+ ObjectClasses attnObjectClasses = clazz.getAnnotation(ObjectClasses.class);
+ NamingSuffix attnNamingSuffix = clazz.getAnnotation(NamingSuffix.class);
+ this.namingAttribute = attnNamingAttr != null ? attnNamingAttr.value() : null;
+ this.objectClasses = attnObjectClasses != null ? attnObjectClasses.value() : null;
+ this.namingSuffixElements = attnNamingSuffix != null ? attnNamingSuffix.value() : null;
+
+ if (this.namingAttribute == null)
+ {
+ throw new ContextMapperException("Error creating context mapper for class: "
+ + clazz.getSimpleName()
+ + ". The @NamingAttribute annotation is required.");
+ }
+
+ if (this.objectClasses == null)
+ {
+ throw new ContextMapperException("Error creating context mapper for class: "
+ + clazz.getSimpleName()
+ + ". The @ObjectClasses annotation is required.");
+ }
+
+ if (namingSuffixElements == null || namingSuffixElements.length == 0)
+ {
+ throw new ContextMapperException("Error creating context mapper for class: "
+ + clazz.getSimpleName()
+ + ". The @NamingSuffix annotation is required.");
+ }
+
+ if (namingSuffixElements[0].contains(","))
+ {
+ throw new ContextMapperException("Error creating context mapper for class: "
+ + clazz.getSimpleName()
+ + ". Naming path nodes must be defined as separate elements, "
+ + "for example: @NamingSuffix({\"ou=people\", \"dc=example\", \"dc=com\"}), and not: "
+ + "@NamingSuffix({\"ou=people,dc=example,dc=com\"})");
+ }
+ }
+
+ private void buildNamingSuffix() throws ContextMapperException
+ {
+ namingSuffix = new DistinguishedName();
+ for (int i = namingSuffixElements.length - 1; i >= 0; i--)
+ {
+ String[] nameValue = namingSuffixElements[i].split("=");
+ if (nameValue.length != 2)
+ {
+ throw new ContextMapperException("Error creating context mapper for class: " + clazz.getSimpleName()
+ + ". Syntax error in naming suffix element: " + namingSuffixElements[i]);
+ }
+ namingSuffix.add(nameValue[0].trim(), nameValue[1].trim());
+ }
+ }
+
+ private void mapAttributesToBeanProperties()
+ {
+ contextMap = new ContextMap();
+ for (Field field : clazz.getDeclaredFields())
+ {
+ DirAttribute dirAttribute = field.getAnnotation(DirAttribute.class);
+ if (dirAttribute != null)
+ {
+ String beanPropertyName = StringUtils.capitalize(field.getName());
+ String attributeName = dirAttribute.value().equals("") ? field.getName() : dirAttribute.value();
+ contextMap.map(beanPropertyName, attributeName);
+ }
+ }
+ }
+
+ private void cacheGettersAndSetters() throws ContextMapperException
+ {
+ propertyGetters = new HashMap();
+ propertySetters = new HashMap();
+
+ for (String beanPropertyName : contextMap.beanPropertyNames())
+ {
+ try
+ {
+ Method getter = clazz.getMethod("get" + beanPropertyName);
+ propertyGetters.put(beanPropertyName, getter);
+ Method setter = clazz.getMethod("set" + beanPropertyName, getter.getReturnType());
+ propertySetters.put(beanPropertyName, setter);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new ContextMapperException("Error creating context mapper for class: "
+ + clazz.getSimpleName()
+ + ". " + clazz.getSimpleName() + " is missing a getter/setter for property: "
+ + beanPropertyName, e);
+ }
+ }
+ }
+
+ private void registerReferencedEntries() throws ContextMapperException
+ {
+ for (Method getter : propertyGetters.values())
+ {
+ Class> returnType = getter.getReturnType();
+ if (!ValidConversionType.isValidConversionType(returnType))
+ {
+ try
+ {
+ Class componentType = returnType.isArray() ? returnType.getComponentType() : returnType;
+ typeConverter.registerCustomEditor(componentType,
+ refEditorFactory.referencedEntryEditorForClass(componentType));
+ }
+ catch (ReferencedEntryEditorCreationException e)
+ {
+ throw new ContextMapperException(clazz.getSimpleName() + "."
+ + getter.getName()
+ + "() has invalid return type. Return type must be a context mapped "
+ + "directory object, or one of the following: "
+ + ValidConversionType.listTypes(), e);
+ }
+ }
+ }
+ }
+
+ public Object mapFromContext(Object ctx)
+ {
+ DirContextAdapter ctxAdapter = (DirContextAdapter) ctx;
+ Object instance;
+ try
+ {
+ instance = clazz.newInstance();
+ for (String beanPropertyName : contextMap.beanPropertyNames())
+ {
+ Class attributeType = propertyGetters.get(beanPropertyName).getReturnType();
+ Attribute attribute =
+ ctxAdapter.getAttributes().get(contextMap.attributeNameFor(beanPropertyName));
+ if (attribute != null)
+ {
+ try
+ {
+ Object beanPropertyValue = typeConverter.convertIfNecessary(
+ new AttributeDecorator(attribute).getAllAsObject(), attributeType);
+ propertySetters.get(beanPropertyName).invoke(instance, beanPropertyValue);
+ }
+ catch (TypeMismatchException e)
+ {
+ throw new ContextMapperException("Cannot set bean property '" + beanPropertyName
+ + "' in class " + clazz.getSimpleName() + ", " + e.getMessage(), e);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ return instance;
+ }
+
+ // TODO: Simplify this method: When bug with DirContextAdapter.setAttribute() (LDAP-15) is fixed,
+ // TODO: register a type converter to convert to type Attribute
+ public void mapToContext(Object beanInstance, Object ctx)
+ {
+ DirContextAdapter ctxAdapter = (DirContextAdapter) ctx;
+ ctxAdapter.setAttributeValues("objectclass", objectClasses);
+ for (String attributeName : contextMap.attributeNames())
+ {
+ try
+ {
+ Object beanPropertyValue = propertyGetters.get(
+ contextMap.beanPropertyNameFor(attributeName)).invoke(beanInstance);
+ LOGGER.trace("mapToContext() attribute:" + attributeName + ", value: " + beanPropertyValue);
+
+ if (beanPropertyValue != null)
+ {
+ if (beanPropertyValue instanceof byte[])
+ {
+ ctxAdapter.setAttributeValue(attributeName, beanPropertyValue);
+ }
+ else if (beanPropertyValue instanceof Object[])
+ {
+ ctxAdapter.setAttributeValues(attributeName,
+ typeConverter.getAllAsText((Object[]) beanPropertyValue));
+ }
+ else
+ {
+ ctxAdapter.setAttributeValue(attributeName,
+ typeConverter.getAsText(beanPropertyValue));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ }
+
+ public Name buildDn(Object beanInstance) throws ContextMapperException
+ {
+ if (beanInstance == null)
+ {
+ throw new ContextMapperException("Can't build Dn from beanInstance, beanInstance is null");
+ }
+ return buildDn(namingAttributeValue(beanInstance));
+ }
+
+ public Name buildDn(String namingAttributeValue) throws ContextMapperException
+ {
+ if (namingAttributeValue == null)
+ {
+ throw new ContextMapperException("Can't build Dn from namingAttributeValue, namingAttributeValue is null");
+ }
+ DistinguishedName dn = (DistinguishedName) namingSuffix.clone();
+ dn.add(namingAttribute, namingAttributeValue);
+ LOGGER.trace("buildDn(): " + dn);
+ return dn;
+ }
+
+ public String namingAttributeValue(Object beanInstance)
+ {
+ Method method = propertyGetters.get(contextMap.beanPropertyNameFor(namingAttribute));
+ try
+ {
+ return (String) method.invoke(clazz.cast(beanInstance));
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ public ContextMap getContextMap()
+ {
+ return contextMap;
+ }
+
+ public String[] getObjectClasses()
+ {
+ return objectClasses;
+ }
+
+ public String getNamingAttribute()
+ {
+ return namingAttribute;
+ }
+
+ public DistinguishedName getNamingSuffix()
+ {
+ return namingSuffix;
+ }
+}
\ No newline at end of file
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/AttributeDecorator.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/AttributeDecorator.java
new file mode 100644
index 00000000..ccd3ec96
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/AttributeDecorator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.contextmapping;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+
+public class AttributeDecorator
+{
+ private Attribute attribute;
+
+ public AttributeDecorator(Attribute attribute)
+ {
+ this.attribute = attribute;
+ }
+
+ public Object getAllAsObject() throws NamingException
+ {
+ if (attribute != null)
+ {
+ if (attribute.size() == 1)
+ {
+ return attribute.get(0);
+ }
+ else
+ {
+ Object[] o = new Object[attribute.size()];
+ for (int i = 0; i < attribute.size(); i++)
+ {
+ o[i] = attribute.get(i);
+ }
+ return o;
+ }
+ }
+ else
+ {
+ return null;
+ }
+
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/ContextMap.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/ContextMap.java
new file mode 100644
index 00000000..aae1d046
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/ContextMap.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.contextmapping;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** A bi-directional map of bean property names to attribute names */
+public class ContextMap
+{
+ private static final Log LOGGER = LogFactory.getLog(ContextMap.class);
+ private Map beanPropertyNameKeys;
+ private Map attributeNameKeys;
+
+ public ContextMap()
+ {
+ beanPropertyNameKeys = new HashMap();
+ attributeNameKeys = new HashMap();
+ }
+
+ public void map(String beanPropertyName, String toAttributeName)
+ {
+ LOGGER.info("Bean property '" + beanPropertyName + "' maps to ldap attribute '" + toAttributeName + "'");
+ beanPropertyNameKeys.put(beanPropertyName, toAttributeName);
+ attributeNameKeys.put(toAttributeName, beanPropertyName);
+ }
+
+ public String beanPropertyNameFor(String attributeName)
+ {
+ return attributeNameKeys.get(attributeName);
+ }
+
+ public String attributeNameFor(String beanPropertyName)
+ {
+ return beanPropertyNameKeys.get(beanPropertyName);
+ }
+
+ public Set beanPropertyNames()
+ {
+ return beanPropertyNameKeys.keySet();
+ }
+
+ public Set attributeNames()
+ {
+ return attributeNameKeys.keySet();
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/ContextMapperFactory.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/ContextMapperFactory.java
new file mode 100644
index 00000000..bf234a62
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/ContextMapperFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.contextmapping;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.ldap.odm.attributetypes.LdapTypeConverter;
+import org.springframework.ldap.odm.attributetypes.ReferencedEntryEditorFactory;
+import org.springframework.ldap.odm.contextmapping.exception.ContextMapperException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ContextMapperFactory
+{
+ private static final Log LOGGER = LogFactory.getLog(ContextMapperFactory.class);
+ private Map contextMappers;
+ private LdapTypeConverter typeConverter;
+ private ReferencedEntryEditorFactory referencedEntryEditorFactory;
+
+ public ContextMapperFactory(LdapTypeConverter typeConverter,
+ ReferencedEntryEditorFactory referencedEntryEditorFactory)
+ {
+ contextMappers = new HashMap();
+ this.typeConverter = typeConverter;
+ this.referencedEntryEditorFactory = referencedEntryEditorFactory;
+ this.referencedEntryEditorFactory.setContextMapperFactory(this);
+ }
+
+ public AnnotatedClassContextMapper contextMapperForClass(Class clazz)
+ throws ContextMapperException
+ {
+ if (contextMappers.containsKey(clazz))
+ {
+ LOGGER.debug("Returning cached context mapper for class: " + clazz.getSimpleName());
+ return contextMappers.get(clazz);
+ }
+ else
+ {
+ LOGGER.debug("Attempting to create a context mapper for class: " + clazz.getSimpleName());
+ AnnotatedClassContextMapper contextMapper =
+ new AnnotatedClassContextMapper(clazz, typeConverter, referencedEntryEditorFactory);
+ contextMappers.put(clazz, contextMapper);
+ return contextMapper;
+ }
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/exception/ContextMapperException.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/exception/ContextMapperException.java
new file mode 100644
index 00000000..66863897
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/contextmapping/exception/ContextMapperException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.contextmapping.exception;
+
+public class ContextMapperException extends Exception
+{
+
+
+ public ContextMapperException(String message)
+ {
+ super(message);
+ }
+
+ public ContextMapperException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+
+
+
+
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/LdapDao.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/LdapDao.java
new file mode 100644
index 00000000..443400d1
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/LdapDao.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.dao;
+
+import java.util.List;
+
+import javax.naming.Name;
+
+public interface LdapDao
+{
+ void create(Object dirObject);
+
+ void update(Object dirObject);
+
+ void createOrUpdate(Object dirObject);
+
+ void delete(Object dirObject);
+
+ Object findByNamingAttribute(String namingValue, Class returnType);
+
+ public Object findByDn(Name dn, Class returnType);
+
+ List findAll(Class ofType);
+
+ List filterByBeanProperty(String value, String beanPropertyName, Class returnType);
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/LdapDaoImpl.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/LdapDaoImpl.java
new file mode 100644
index 00000000..fe478807
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/LdapDaoImpl.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.dao;
+
+import java.util.List;
+
+import javax.naming.Name;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.ldap.core.DirContextAdapter;
+import org.springframework.ldap.core.DistinguishedName;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.odm.contextmapping.AnnotatedClassContextMapper;
+import org.springframework.ldap.odm.contextmapping.ContextMapperFactory;
+import org.springframework.ldap.odm.contextmapping.exception.ContextMapperException;
+import org.springframework.ldap.odm.dao.exception.DaoException;
+import org.springframework.ldap.odm.dao.exception.DataIntegrityViolationException;
+import org.springframework.ldap.odm.dao.exception.EntryNotFoundException;
+
+public class LdapDaoImpl implements LdapDao
+{
+ static final Log LOGGER = LogFactory.getLog(LdapDaoImpl.class);
+ LdapTemplate ldapTemplate;
+ ContextMapperFactory ctxMapperFactory;
+
+ public LdapDaoImpl(LdapTemplate ldapTemplate, ContextMapperFactory ctxMapperFactory)
+ {
+ this.ldapTemplate = ldapTemplate;
+ this.ctxMapperFactory = ctxMapperFactory;
+ }
+
+ public void create(Object dirObject)
+ {
+ DirContextAdapter context = new DirContextAdapter();
+ try
+ {
+ AnnotatedClassContextMapper ctxMapper = ctxMapperFactory.contextMapperForClass(dirObject.getClass());
+ ctxMapper.mapToContext(dirObject, context);
+ ldapTemplate.bind(ctxMapper.buildDn(dirObject), context, null);
+ }
+ catch (org.springframework.dao.DataIntegrityViolationException dive)
+ {
+ throw new DataIntegrityViolationException(dive.getMessage());
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+ }
+
+ public void update(Object dirObject)
+ {
+ try
+ {
+ AnnotatedClassContextMapper ctxMapper = ctxMapperFactory.contextMapperForClass(dirObject.getClass());
+ Name dn = ctxMapper.buildDn(dirObject);
+ DirContextAdapter contextAdapter = (DirContextAdapter) ldapTemplate.lookup(dn);
+ ctxMapper.mapToContext(dirObject, contextAdapter);
+ ldapTemplate.modifyAttributes(dn, contextAdapter.getModificationItems());
+ }
+ catch (org.springframework.ldap.NameNotFoundException e)
+ {
+ //We need to rethrow a serializable exception if we are going to invoke
+ //this as a remote service.
+ throw new EntryNotFoundException(e.getMessage());
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+ }
+
+ public void createOrUpdate(Object dirObject)
+ {
+ try
+ {
+ AnnotatedClassContextMapper ctxMapper = ctxMapperFactory.contextMapperForClass(dirObject.getClass());
+ if (findByDn(ctxMapper.buildDn(dirObject), dirObject.getClass()) == null)
+ {
+ create(dirObject);
+ }
+ else
+ {
+ update(dirObject);
+ }
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+
+ }
+
+ public void delete(Object dirObject)
+ {
+ try
+ {
+ AnnotatedClassContextMapper ctxMapper = ctxMapperFactory.contextMapperForClass(dirObject.getClass());
+ ldapTemplate.unbind(ctxMapper.buildDn(dirObject));
+ }
+ catch (ContextMapperException e)
+ {
+ e.printStackTrace();
+ throw new DaoException(e.getMessage(), e);
+ }
+ }
+
+ public Object findByNamingAttribute(String namingAttributeValue, Class returnType)
+ {
+ try
+ {
+ AnnotatedClassContextMapper ctxMapper = ctxMapperFactory.contextMapperForClass(returnType);
+ Name dn = ctxMapper.buildDn(namingAttributeValue);
+ return findByDn(dn, returnType);
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+ }
+
+ public Object findByDn(Name dn, Class returnType)
+ {
+ try
+ {
+ AnnotatedClassContextMapper ctxMapper = ctxMapperFactory.contextMapperForClass(returnType);
+ return ldapTemplate.lookup(dn, ctxMapper);
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+ catch (RuntimeException e)
+ {
+ return null;
+ }
+ }
+
+
+ public List filterByBeanProperty(String value, String beanPropertyName, Class returnType)
+ {
+ try
+ {
+ LOGGER.debug("Filtering on property: " + beanPropertyName + ", for value: " + value);
+ AnnotatedClassContextMapper ctxMapper =
+ ctxMapperFactory.contextMapperForClass(returnType);
+
+ try
+ {
+ String attributeName = ctxMapper.getContextMap().attributeNameFor(beanPropertyName);
+ String filter = attributeName + "=" + value;
+ List results = ldapTemplate.search(ctxMapper.getNamingSuffix().toString(), filter,
+ ctxMapperFactory.contextMapperForClass(returnType));
+ LOGGER.debug(results.size() + " results found for filter: " + filter);
+ return results;
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+ }
+
+ public List findAll(Class ofType)
+ {
+ try
+ {
+ AnnotatedClassContextMapper ctxMapper = ctxMapperFactory.contextMapperForClass(ofType);
+ StringBuilder filterBuilder = new StringBuilder();
+ filterBuilder.append("(&");
+ for (String objectClass : ctxMapper.getObjectClasses())
+ {
+ filterBuilder.append("(objectClass=");
+ filterBuilder.append(objectClass);
+ filterBuilder.append(")");
+ }
+ filterBuilder.append(")");
+ List results = ldapTemplate.search(DistinguishedName.EMPTY_PATH, filterBuilder.toString(), ctxMapper);
+ LOGGER.debug("Found " + results.size() + " entries of type " + ofType.getSimpleName());
+ return results;
+ }
+ catch (ContextMapperException e)
+ {
+ throw new DaoException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/DaoException.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/DaoException.java
new file mode 100644
index 00000000..9ca7abfd
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/DaoException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.dao.exception;
+
+public class DaoException extends RuntimeException
+{
+ public DaoException(String message)
+ {
+ super(message);
+ }
+
+ public DaoException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/DataIntegrityViolationException.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/DataIntegrityViolationException.java
new file mode 100644
index 00000000..d2d015b7
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/DataIntegrityViolationException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.dao.exception;
+
+import java.util.List;
+
+/**
+ * A Serializable version of org.springframework.dao.DataIntegrityViolationException
+ */
+public class DataIntegrityViolationException extends DaoException
+{
+
+ private List nonUniqueAttributes;
+
+ public DataIntegrityViolationException(String message)
+ {
+ super(message);
+ }
+
+ public DataIntegrityViolationException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+
+
+
+
+}
diff --git a/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/EntryNotFoundException.java b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/EntryNotFoundException.java
new file mode 100644
index 00000000..0274c8f0
--- /dev/null
+++ b/spring-ldap-odm/src/main/java/org/springframework/ldap/odm/dao/exception/EntryNotFoundException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.dao.exception;
+
+/**
+ * A Serializable version of org.springframework.dao.EntryNotFoundException
+ */
+public class EntryNotFoundException extends DaoException
+{
+
+ public EntryNotFoundException(String message)
+ {
+ super(message);
+ }
+
+}
diff --git a/spring-ldap-odm/src/test/java/org/springframework/ldap/odm/attributetypes/LdapTypeConverterTest.java b/spring-ldap-odm/src/test/java/org/springframework/ldap/odm/attributetypes/LdapTypeConverterTest.java
new file mode 100644
index 00000000..88966e2e
--- /dev/null
+++ b/spring-ldap-odm/src/test/java/org/springframework/ldap/odm/attributetypes/LdapTypeConverterTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2006 by Majitek. All Rights Reserved.
+ *
+ * This software is the proprietary information of Majitek. Use is subject to license terms.
+ */
+package org.springframework.ldap.odm.attributetypes;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.ldap.core.DistinguishedName;
+import org.testng.Assert;
+
+public class LdapTypeConverterTest extends TestCase {
+ private static final Log LOGGER = LogFactory
+ .getLog(LdapTypeConverterTest.class);
+
+ private LdapTypeConverter typeConverter;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ typeConverter = new LdapTypeConverter();
+ }
+
+ public void testConvertToByteArray() {
+ byte[] objectToTranslate = "fred".getBytes();
+
+ try {
+ byte[] translated = (byte[]) typeConverter.convertIfNecessary(
+ objectToTranslate, byte[].class);
+ Assert.assertTrue(Arrays.equals(objectToTranslate, translated),
+ "byte[] attributes should pass through unchanged.");
+ } catch (TypeMismatchException e) {
+ Assert
+ .fail(
+ "Unexpected exception: byte[] attributes should pass through unchanged.",
+ e);
+ }
+ }
+
+ public void testConvertToBoolean() {
+ String objectToTranslate = "true";
+ try {
+ Object translated = typeConverter.convertIfNecessary(
+ objectToTranslate, Boolean.class);
+ Assert
+ .assertEquals(true, translated,
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertFromBoolean() {
+ boolean objectToTranslate = false;
+ try {
+ Object translated = typeConverter.getAsText(objectToTranslate);
+ Assert.assertEquals(translated, "false",
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToString() {
+ String objectToTranslate = "onetwothree";
+ try {
+ Object translated = typeConverter.convertIfNecessary(
+ objectToTranslate, String.class);
+ Assert.assertEquals("onetwothree", translated,
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToStringArray() {
+ String object1 = "onetwothree";
+ String object2 = "fourfivesix";
+ String object3 = "seveneightnine";
+ Object objectToTranslate = new String[] { object1, object2, object3 };
+
+ try {
+ Object[] translated = (Object[]) typeConverter.convertIfNecessary(
+ objectToTranslate, String[].class);
+ Assert.assertEquals("fourfivesix", translated[1],
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToGeneralizedTime() throws ParseException {
+ String object1 = "20061005093637.44";
+ DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss.S");
+ Date expected = df.parse("20061005093637.44");
+
+ try {
+ Object translated = typeConverter.convertIfNecessary(object1,
+ Date.class);
+ Assert.assertEquals(translated, expected,
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToGeneralizedTimeArray() throws ParseException {
+ String object1 = "20071105093655.44";
+ String object2 = "20061005093637.44";
+ Object objectToTranslate = new String[] { object1, object2 };
+
+ DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss.S");
+ Date expected = df.parse("20061005093637.44");
+
+ try {
+ Object[] translated = (Object[]) typeConverter.convertIfNecessary(
+ objectToTranslate, Date[].class);
+ Assert.assertEquals(translated[1], expected,
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation");
+ }
+ }
+
+ public void testConvertToLdapName() {
+ String object1 = "uid=amAdmin, ou = people, dc = myretsu,dc=com";
+ try {
+ Object translated = typeConverter.convertIfNecessary(object1,
+ LdapName.class);
+ Assert.assertEquals(translated, new LdapName(object1),
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ } catch (InvalidNameException e) {
+ Assert.fail("Problem with test: Ldap name can't be parsed.", e);
+ }
+ }
+
+ public void testConvertToLdapNameArray() {
+ String object1 = "uid=amAdmin, ou = people, dc = myretsu,dc=com";
+ String object2 = "uid=fred, ou = people, dc = myretsu,dc=com";
+ Object objectToTranslate = new String[] { object1, object2 };
+
+ try {
+ Object[] translated = (Object[]) typeConverter.convertIfNecessary(
+ objectToTranslate, LdapName[].class);
+ Assert.assertEquals(new LdapName(object2), translated[1],
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ } catch (InvalidNameException e) {
+ Assert.fail("Problem with test: Ldap name can't be parsed.", e);
+ }
+ }
+
+ public void testConvertToDistinguishedName() {
+ String object1 = "uid=amAdmin, ou = people, dc = myretsu,dc=com";
+ try {
+ Object translated = typeConverter.convertIfNecessary(object1,
+ DistinguishedName.class);
+ Assert.assertEquals(new DistinguishedName(object1), translated,
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToDistinguishedNameArray() {
+ String object1 = "uid=amAdmin, ou = people, dc = myretsu,dc=com";
+ String object2 = "uid=fred, ou = people, dc = myretsu,dc=com";
+ Object objectToTranslate = new String[] { object1, object2 };
+
+ try {
+ Object[] translated = (Object[]) typeConverter.convertIfNecessary(
+ objectToTranslate, DistinguishedName[].class);
+ Assert.assertEquals(new DistinguishedName(object2), translated[1],
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToLong() {
+ String object1 = "9887342";
+ try {
+ Object translated = typeConverter.convertIfNecessary(object1,
+ Long.class);
+ Assert.assertEquals(9887342L, translated,
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToLongArray() {
+ String object1 = "878787";
+ String object2 = "23948787";
+ Object objectToTranslate = new String[] { object1, object2 };
+
+ try {
+ Object[] translated = (Object[]) typeConverter.convertIfNecessary(
+ objectToTranslate, Long[].class);
+ Assert.assertEquals(23948787L, translated[1],
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToInteger() {
+ String object1 = "9887342";
+
+ try {
+ Object translated = typeConverter.convertIfNecessary(object1,
+ Integer.class);
+ Assert.assertEquals(9887342, translated,
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testConvertToIntegerArray() {
+ String object1 = "878787";
+ String object2 = "23948787";
+ Object objectToTranslate = new String[] { object1, object2 };
+
+ try {
+ Object[] translated = (Object[]) typeConverter.convertIfNecessary(
+ objectToTranslate, Integer[].class);
+ Assert.assertEquals(23948787, translated[1],
+ "Unexpected translated value");
+ } catch (TypeMismatchException e) {
+ Assert.fail("Unexpected exception during translation", e);
+ }
+ }
+
+ public void testThrowsTypeMismatchExceptionWhenTypeTranslationFails() {
+ String object1 = "asdlkjkalkjl";
+
+ Object[] translated;
+ try {
+ translated = (Object[]) typeConverter.convertIfNecessary(object1,
+ LdapName.class);
+ Assert.fail("Should've thrown exception");
+ } catch (TypeMismatchException e) {
+ // Pass
+ }
+ }
+
+}