Introduction
In this article Spring LDAP which provides a simplified wrapper framework around LDAP implementations is covered in detail. This article assumes that the reader has a basic understanding on Spring framework and LDAP directory server. The first section of the article covers the various operations that can be performed on LDAP. Support for parsing externally stored LDAP data is also covered with the help of LDIF data. The ODM Manager APIs for mapping LDAP objects directly to java objects have also been explored in this article. If you are beginner in learning spring framework, please read Introduction to Spring Framework.
also read:
Download Spring LDAP Sample Code
- [download id=”5″]
LDAP operations
In this section, we will illustrate the usage of various operations that can be performed on LDAP directory using Spring. The examples given here are tested with Apache Directory server, however the code should work with any LDAP implementations. The operations that will be getting covered in the following sections are,
- Searching
- Adding objects
- Removing objects
- Modifying objects
Searching objects
For illustrating search operations in LDAP, we will create objects under the node system/users. We will be creating objects of type ‘person’ in this node. Note that for having an object oriented access for the ‘person’ type, we will create a java class ‘User’ that maps to it. Have a look at the declaration of this class.
package net.javabeat.articles.spring.ldap; public class User { private String commonName; private String telephone; public String getCommonName() { return commonName; } public void setCommonName(String commonName) { this.commonName = commonName; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this.telephone = telephone; } public String toString(){ return commonName + "-" + telephone; } }
The class definition is pretty simple. It defines two simple properties ‘commonName’ and ‘telephone’. The search class that makes use of Spring libraries is given below. Go through the following code carefully.
package net.javabeat.articles.spring.ldap.operations; import java.util.HashSet; import java.util.Set; import net.javabeat.articles.spring.ldap.User; import net.javabeat.articles.spring.ldap.UserAttributesMapper; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.ldap.core.LdapTemplate; public class SimpleSearch { private LdapTemplate ldapTemplate; @SuppressWarnings("unchecked") public Set getAllUsers(){ UserAttributesMapper mapper = new UserAttributesMapper(); return new HashSet( ldapTemplate.search("ou=users,ou=system", "(objectClass=person)", mapper)); } public void setLdapTemplate(LdapTemplate ldapTemplate){ this.ldapTemplate = ldapTemplate; } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("search.xml"); SimpleSearch simpleSearch = new SimpleSearch(); simpleSearch.setLdapTemplate(context.getBean("ldapTemplate", LdapTemplate.class)); for (User user : simpleSearch.getAllUsers()){ System.out.println(user); } } }
Note that the class defines an instance of LdapTemplate class. This template class follows the template pattern and is very similar to the existing template classes like JdbcTemplate, JmsTemplate etc. Performing operations on the LDAP directory becomes simple with this template class. Note that before using this template class, it has to be configured with various properties such as ‘username’, ‘password’ and the url of the directory server. We will see the configuration in the later section of this article.
Carefully examine the method getAllUsers(). This method calls the search() method defined on LdapTemplate for searching objects in the directory server. Note that this method takes two parameters, the first being the base domain name and the second one defines the filter. In our case, since the objects are located under the node system/users, the base domain name happens to be ‘ou=users,ou=system’, here ‘ou’ stands for organizational unit. For the second parameter we have used the value ‘objectClass=person’ which means that we want to search objects of type ‘person’. Note that since LDAP can store objects of any type, the return objects have to be mapped to some implementation representing a custom model and for the same purpose, we have used the customized implementation of ‘AttributesMapper’ implementation which is ‘UserAttributesMapper’.
package net.javabeat.articles.spring.ldap; import javax.naming.NamingException; import javax.naming.directory.Attributes; import org.springframework.ldap.core.AttributesMapper; public class UserAttributesMapper implements AttributesMapper { @Override public User mapFromAttributes(Attributes attributes) throws NamingException { User userObject = new User(); String commonName = (String)attributes.get("cn").get(); userObject.setCommonName(commonName); if (attributes.get("telephoneNumber") == null){ System.out.println("Telephone is null for " + commonName); }else{ String telephone = attributes.get("telephoneNumber").get().toString(); userObject.setTelephone(telephone); } return userObject; } }
Note that in the above class, the method mapFromAttributes() is overridden which takes the input as ‘Attributes’ which represents a general purpose attribute collection. The implementation creates a customized flavor of model implementation by gathering the relevant attributes and then constructs the object accordingly.
package net.javabeat.articles.spring.ldap.operations; import java.util.HashSet; import java.util.Set; import net.javabeat.articles.spring.ldap.User; import net.javabeat.articles.spring.ldap.UserAttributesMapper; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.filter.AndFilter; import org.springframework.ldap.filter.EqualsFilter; public class DynamicSearch { private LdapTemplate ldapTemplate; @SuppressWarnings("unchecked") public Set getAllUsers(String surName){ UserAttributesMapper mapper = new UserAttributesMapper(); AndFilter filterObject = new AndFilter(); filterObject.and(new EqualsFilter("objectClass", "person")); filterObject.and(new EqualsFilter("sn", surName)); return new HashSet( ldapTemplate.search("ou=users,ou=system", filterObject.encode(), mapper)); } public void setLdapTemplate(LdapTemplate ldapTemplate){ this.ldapTemplate = ldapTemplate; } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("search.xml"); DynamicSearch dynamicSearch = new DynamicSearch(); dynamicSearch.setLdapTemplate(context.getBean("ldapTemplate", LdapTemplate.class)); for (User user : dynamicSearch.getAllUsers("David")){ System.out.println(user); } } }
A variation of searching objects dynamically in the directory server is given above. In the above code, the obvious variation is the search filter being dynamically constructed using ‘Filter’ APIs. We have dynamically constructed search parameters ‘objectClass’, ‘sn’ and have and’ed them using ‘AndFilter’. Rest of the code in the above section remains the same.
Adding objects
In this section, we will see how to add objects in the LDAP directory. For adding objects, the bind() defined on LdapTemplate can be used. Note that the bind() methods takes the distinguished name and the list of attributes as parameters. Note that the unique distinguished name represents the combination of base name and the common name. The base name is ‘ou=users,ou=system’ in our case and the common name will come as user input.
package net.javabeat.articles.spring.ldap.operations; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.BasicAttributes; import net.javabeat.articles.spring.ldap.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; public class AddUser { private LdapTemplate ldapTemplate; public void add(String commonName, String surName, String telephone){ String baseDn = "ou=users,ou=system"; DistinguishedName distinguisedName = new DistinguishedName(baseDn); distinguisedName.add("cn", commonName); Attributes userAttributes = new BasicAttributes(); userAttributes.put("sn", surName); userAttributes.put("telephoneNumber", telephone); BasicAttribute classAttribute = new BasicAttribute("objectclass"); classAttribute.add("top"); classAttribute.add("person"); userAttributes.put(classAttribute); ldapTemplate.bind(distinguisedName, null, userAttributes); } public void setLdapTemplate(LdapTemplate ldapTemplate){ this.ldapTemplate = ldapTemplate; } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("search.xml"); LdapTemplate ldapTemplate = context.getBean("ldapTemplate", LdapTemplate.class); AddUser addPerson = new AddUser(); addPerson.setLdapTemplate(ldapTemplate); addPerson.add("New User", "User", "12345"); SimpleSearch simpleSearch = new SimpleSearch(); simpleSearch.setLdapTemplate(ldapTemplate); for (User user : simpleSearch.getAllUsers()){ System.out.println(user); } } }
For adding simple attributes, we can use the put() method defined on the BasicAttributes class. The attribute ‘objectClass’ is a multi-valued attribute. By default, any object defined in LDAP will have the ‘objectClass’ set to ‘top’. Hence, in our case, the object type for the person objects will be {‘top’, ‘person’}. Hence for adding a multi-valued attribute such as ‘objectClass’, the attribute values have to be packaged as a BasicAttribute object and then have to be added to the main attribute.
Removing objects
For removing objects from the directory server, the method unbind() can be used. Because a distinguished name represents a unique name in the directory server, the unbind() method takes an instance of distinguished name as an argument.
package net.javabeat.articles.spring.ldap.operations; import net.javabeat.articles.spring.ldap.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; public class RemoveUser { private LdapTemplate ldapTemplate; public void remove(String commonName){ String baseDn = "ou=users,ou=system"; DistinguishedName distinguisedName = new DistinguishedName(baseDn); distinguisedName.add("cn", commonName); ldapTemplate.unbind(distinguisedName); } public void setLdapTemplate(LdapTemplate ldapTemplate){ this.ldapTemplate = ldapTemplate; } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("search.xml"); LdapTemplate ldapTemplate = context.getBean("ldapTemplate", LdapTemplate.class); RemoveUser removePerson = new RemoveUser(); removePerson.setLdapTemplate(ldapTemplate); removePerson.remove("New User"); SimpleSearch simpleSearch = new SimpleSearch(); simpleSearch.setLdapTemplate(ldapTemplate); for (User user : simpleSearch.getAllUsers()){ System.out.println(user); } } }
The above code tries to remove a person and then queries for the list of person objects in the directory server.
Modifying objects
Existing objects can be modified by invoking the method modifyAttributes() defined on the LdapTemplate class. Note that the arguments to this method are the distinguished name and the list of attributes to be modified.
package net.javabeat.articles.spring.ldap.operations; import javax.naming.directory.Attribute; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import net.javabeat.articles.spring.ldap.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; public class ModifyUser { private LdapTemplate ldapTemplate; public void modify(String commonName, String telephone){ String baseDn = "ou=users,ou=system"; DistinguishedName distinguisedName = new DistinguishedName(baseDn); distinguisedName.add("cn", commonName); Attribute telephoneAttribute = new BasicAttribute("telephoneNumber", telephone); ModificationItem telephoneItem = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, telephoneAttribute); ldapTemplate.modifyAttributes(distinguisedName, new ModificationItem[]{telephoneItem}); } public void setLdapTemplate(LdapTemplate ldapTemplate){ this.ldapTemplate = ldapTemplate; } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("search.xml"); LdapTemplate ldapTemplate = context.getBean("ldapTemplate", LdapTemplate.class); ModifyUser modifyPerson = new ModifyUser(); modifyPerson.setLdapTemplate(ldapTemplate); modifyPerson.modify("Steve David", "9999999"); SimpleSearch simpleSearch = new SimpleSearch(); simpleSearch.setLdapTemplate(ldapTemplate); for (User user : simpleSearch.getAllUsers()){ System.out.println(user); } } }
Each attribute to be modified must be represented through a ModificationItem object which specifies the flag whether the value of the existing attribute has to be replaced or a new attribute can be created of the attribute if it doesn’t exist. In the above sample, for modifying the ‘telephoneNumber’ attribute, we have used the REPLACE_ATTRIBUTE flag which will replace the existing attribute value for the person object.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> <bean id="ldapTemplate"> <property name="contextSource" ref="contextSource" /> </bean> <bean id="contextSource"> <property name="url" value="ldap://localhost:10389" /> <property name="userDn" value="uid=admin,ou=system" /> <property name="password" value="secret" /> <property name="pooled" value="false" /> </bean> </beans>
The Spring configuration file for configuring LdapTemplate instance is given below. Note that the property ‘contextSource’ has to be populated. The ‘contextSource’ property is of type ‘LdapContextSource’ and the mandatory properties ‘userDn’, ‘password’ and ‘url’ have to be specified.
LDIF Parser
LDIF stands for LDAP Directory Interchange Format and it represents the way of storing LDAP objects information in a flat file so that they can be easily transported to different machines hosting LDAP servers. Spring LDAP framework provides support for parsing LDAP objects available in a text file and this section illustrates the usage of these APIs.
dn: ou=users,ou=system sn: SN-1 telephoneNumber: 33333 objectClass: person objectClass: top cn: CN-1 dn: ou=users,ou=system sn: SN-2 telephoneNumber: 77777 objectClass: person objectClass: top cn: CN-2
Now we will see how to make use of LDIF parser. The program constructs an instance of LdifParser object and then calls the hasMoreRecords() and getRecord() for reading all attribute information from the text file. A call to getRecord() will retrieve a single record information from the file. The method getAll() will return all the attribute names for the record. Since there is a possibility for an attribute to contain multiple values, we again call getAll() method to collect the attribute values.
package net.javabeat.articles.spring.ldif; import javax.naming.NamingEnumeration; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import org.springframework.core.io.FileSystemResource; import org.springframework.ldap.ldif.parser.LdifParser; import org.springframework.ldap.ldif.parser.Parser; public class LdifTest { public static void main(String[] args) throws Exception{ Parser parser = new LdifParser(); parser.setResource(new FileSystemResource("./bin/users.ldif")); parser.open(); while (parser.hasMoreRecords()){ Attributes attributes = parser.getRecord(); String personDetails = getPersonDetails(attributes); System.out.println(personDetails); } } private static String getPersonDetails(Attributes attributes) throws Exception{ StringBuilder personDetails = new StringBuilder(); personDetails.append("{"); NamingEnumeration attributeNames = attributes.getAll(); while (attributeNames.hasMoreElements()) { Attribute attribute = attributeNames.next(); personDetails.append("[" + attribute.getID()); @SuppressWarnings("unchecked") NamingEnumeration attributeValues = (NamingEnumeration) attribute.getAll(); while (attributeValues.hasMoreElements()){ String attributeValue = attributeValues.next(); personDetails.append("(").append(attributeValue).append(")"); } personDetails.append("]"); } personDetails.append("}"); return personDetails.toString(); } }
ODM Manager
ODM stands for Object Directory Mapping and this facilitates mapping of the LDAP objects directly to java objects with the help of annotations. For this example, we will create a customized type ‘applicationEntity’ under the node system/configuration/services. We will start with the java model class ‘ApplicationEntity’ which will have the core attributes ‘cn’, ‘description’ and ‘presentationAddress’.
package net.javabeat.artices.spring.odm; import java.util.ArrayList; import java.util.List; import javax.naming.Name; import org.springframework.ldap.odm.annotations.Attribute; import org.springframework.ldap.odm.annotations.Entry; import org.springframework.ldap.odm.annotations.Id; @Entry(objectClasses = {"applicationEntity", "top"}) public class ApplicationEntity { @Id private Name distinguisedName; @Attribute(name="cn") private String cn; @Attribute(name="description") private String description; @Attribute(name="presentationAddress") private String presentationAddress; @Attribute(name="objectClass") private List objectClassNames; public ApplicationEntity(){ objectClassNames = new ArrayList(); } public Name getDistinguisedName() { return distinguisedName; } public void setDistinguisedName(Name distinguisedName) { this.distinguisedName = distinguisedName; } public String getCn() { return cn; } public void setCn(String cn) { this.cn = cn; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getPresentationAddress() { return presentationAddress; } public void setPresentationAddress(String presentationAddress) { this.presentationAddress = presentationAddress; } public List getObjectClassNames() { return objectClassNames; } public void setObjectClassNames(List objectClassNames) { this.objectClassNames = objectClassNames; } public String toString(){ return cn + "#" + description + "#" + objectClassNames + "#" + presentationAddress; } }
To specify that the java class maps directly to some ldap object, the annotation ‘@Entry’ has to be used. Note that this annotation has to be configured with the list of object types, in our case, this happens to be ‘top’ and ‘applicationEntiy’. One mandatory attribute of type ‘Name’ representing the distinguished name has to be defined and has to be annotated with ‘@Id’. The attributes for this entity can then be defined, each annotated with ‘@Attribute’ annotation.
package net.javabeat.artices.spring.odm; import java.util.List; import javax.naming.directory.SearchControls; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.odm.core.OdmManager; public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("config.xml"); OdmManager odmManager = context.getBean("odmManager", OdmManager.class); create(odmManager); list(odmManager); read(odmManager); } private static void create(OdmManager odmManager){ ApplicationEntity addressEntity = new ApplicationEntity(); DistinguishedName distinguishedName = new DistinguishedName("ou=services,ou=configuration,ou=system"); distinguishedName.add("cn", "Address"); addressEntity.setDistinguisedName(distinguishedName); addressEntity.setCn("Address"); addressEntity.setDescription("Contains information about the Address entity"); addressEntity.setPresentationAddress("Address"); addressEntity.getObjectClassNames().add("top"); addressEntity.getObjectClassNames().add("applicationEntity"); odmManager.create(addressEntity); } private static void read(OdmManager odmManager){ String baseDn = "ou=services,ou=configuration,ou=system"; DistinguishedName distinguisedName = new DistinguishedName(baseDn); distinguisedName.add("cn", "Book"); ApplicationEntity applicationEntity = odmManager.read(ApplicationEntity.class, distinguisedName); System.out.println(applicationEntity); } private static void list(OdmManager odmManager){ String baseDn = "ou=services,ou=configuration,ou=system"; DistinguishedName distinguisedName = new DistinguishedName(baseDn); SearchControls searchControls = new SearchControls(); List applicationEntityList = odmManager.findAll( ApplicationEntity.class, distinguisedName, searchControls); for (ApplicationEntity applicationEntity : applicationEntityList){ System.out.println(applicationEntity); } } }
For creating a new object, we can use the method create() defined on OdmManager class. An instance of ApplicationEntity is created and the distinguished name is set by calling the setDistinguishedName() method. After that the various attributes like ‘cn’, ‘address’ and ‘presentationAddress’ is populated on the entity object. For reading an entity, the method read() can be used by passing in an instance of distinguished name. For listing all the objects of a particular object type, the method findAll() defined on OdmManager class can be used. This method takes the entity type and the distinguished name as parameters.
also read:
Conclusion
This article started with covering the basics of Spring LDAP by illustrating the various operations such as ‘search’, ‘add’, ‘remove’, ‘modify’ etc that can be performed. Lots of samples were also given for the better illustration of these concepts. LDAP directory data can be stored externally in a file for easier migration and hence Spring LDAP supports parsing data from such files with the help of LDIF Parser. The final section of the article illustrated the usage of ODM Manager which provides a directory mapping between ldap objects and java objects through annotations.
If you have any questions on the spring framework integration with LDAP, please post it in the comments section. Also search in our website to find lot of other interesting articles related to the spring framework. There are some interesting articles about spring framework, interview questions, spring and hibernate integration,etc.
If you would like to receive the future java articles from our website, please subscribe here.