'java'에 해당되는 글 11건

  1. 2007.08.22 개발자 관점에서 본 성능관리의 필요성
  2. 2007.07.26 spring ldap 1

개발자 관점에서 본 성능관리의 필요성

박종현/OOC 선임연구원

현재 많은 애플리케이션이 웹 환경으로 변화하면서 보다 많은 사용자가 언제든지 쉽고 편리하게 원하는 시스템에 접속하기를 바란다. 또 초고속인터넷으로 인해 많은 데이터를 처리할 수 있게 되었다. 하지만 많은 데이터 처리는 트랜잭션 처리량도 같이 증가시켰으며 이는 개발자에게 그 만큼의 부담을 주고 있다. 개발자라면 누구나 고민하는 부분이 자신이 개발한 애플리케이션이 중단되지 않고 잘 운영될 수 있을까 하는 점이다. 이에 J2EE 개발자 입장에서 느끼는 성능관리의 필요성에 대해 지적한다.

애플리케이션을 개발할 때 많은 개발자는 자신이 만든 애플리케이션이 성능을 제대로 발휘할 것인가에 대해 고민한다. 그리고 애플리케이션 개발을 요청한 고객에게 애플리케이션의 성능을 어떻게 객관적으로 제시할 것인가도 중요한 고민거리이다. 이러한 문제는 개발 기간을 단축하는데 장애요인이 된다. 또 배포 후 생길 수 있는 애플리케이션의 오류를 찾는 것은 많은 시간과 노력을 필요로 한다. 실제로 개발 과정에서는 찾지 못했던 애플리케이션의 문제점을 배포 후에 찾게 되는 경우가 많으며 이것을 다시 수정하는 일이 발생하게 된다.

J2EE로 개발한 애플리케이션을 운영하기 위해서는 각 벤더들이 J2EE 스팩으로 구현한 웹 애플리케이션 서버(WAS)를 사용하게 되는데 WAS에 배포된 애플리케이션이 그 안에서 어떻게 운영되고 있는지를 개발자가 이해하기는 상당히 힘들다. J2EE 스팩으로 구현된 각 벤더들의 WAS를 보면 각 벤더의 WAS마다 차이점이 있다. 물론 표준 스팩으로 구현했다 하더라도 그 제품마다 특징이 다르기 때문에 이러한 제품 각각의 특징을 개발자가 다 이해하기 위해서는 많은 시간을 필요로 한다. 하지만 일단 성능상에 문제가 발생하면 개발을 의뢰한 업체는 성능 이슈의 발생원인 및 정확한 위치 등의 구체적인 진단 정보 자료를 요구한다. 그러나 개발자 입장에서는 전술한 이유 등으로 이러한 요구에 부흥하기는 매우 힘들다.

성능관리 솔루션의 필요성

△자바는 시장에 신속하게 대응하고 있다. 하지만 한편으로는 그것이 심각한 위험을 초래할 수 있다. 자바의 높은 추상화 단계로 인해서 자바 개발자들은 실제로 코드가 어떻게 실행되는지 관계없이 제한된 부분만 이해하면 되기 때문이다. 따라서 개발을 진행하면서 일반적인 성능 툴들을 사용하는 것이 성능 유지와 신뢰도에 중요한 부분이 된다. 특히 빠르고 확장성 있는 J2EE 애플리케이션을 관리하고 개발하는 데에 매우 중요하다고 할 수 있다.

기업들이 성공적인 비즈니스를 추진하기 위해서는 안정적인 J2EE 엔터프라이즈 애플리케이션이 중요하다. 제품 생산시에 부하가 큰 곳에서 사소한 성능 문제라도 발생하면 빠르게 진행할 수 있는 상황에서 심각하게 지연 현상이 나타날 수 있다. 따라서 개발 진행상에서 성능 튜닝 툴들을 사용하면 성능 취약성이 무결점 상태까지 적용할 수 있을 것이며 J2EE 애플리케이션 개발의 위험을 최소화할 수 있을 것이다.

△개발 진행 중에 성능 튜닝 툴을 사용함으로써 하드웨어 비용을 줄여서 TCO(PC 한대당 투입되는 전체비용, 즉 하드웨어·소프트웨어·교육·관리비용 등을 모두 통합한 비용을 의미)를 줄일 수 있다.

개발자들은 자신의 코드가 자바의 능력을 최대한 사용하고 하드웨어 자원을 가장 효율적으로 사용하고 있다고 확신하게 된다. 효율적인 애플리케이션은 배포하고 나서 하드웨어 자원을 적게 요구하며 재정적으로나 전략적인 비용을 절감하는 효과를 가져온다.

개발 중에 통상적으로 성능 튜닝 툴을 사용하는 것은 프로젝트가 끝나 가는 시점에서 성능 테스트를 수행하는 전통적인 방법에 비해서 시간과 비용을 절감할 수 있다. 프로젝트가 끝나 가는 시점에서 성능 요인들을 발견한다면 재조직화 해야 하며 프로젝트가 연기되기도 한다. 또 느리고 안정적이지 못한 애플리케이션을 개발하게 됨으로써 오히려 비용이 더 늘어나는 결과를 가져올 수 있다.

△자바의 높은 추상화 단계는 개발자로 하여금 코드가 실행이 될 때, 자바 가상 머신 안에서 어떻게 실행되는지를 이해하는 것을 불가능하게 한다. 자바에서 성능적인 요인은 Native language에 비해 예상하기가 쉽지가 않다. 애플리케이션을 개발하고 나서 각 애플리케이션의 특징을 알아보기 위해서 성능 튜닝 툴을 사용한다면 개발자들은 자신이 개발한 애플리케이션이 실행되는 동안 실제로 내부적으로 어떠한 작업이 수행되는지를 알 수 있다. 이를 통해 개발은 빠르고, 코드는 신뢰있게 하여 발생할 수 있는 성능과 신뢰성에 대한 이슈를 빠르게 추적할 수 있다.

최근 시장에 출시되고 있는 성능관리 제품을 살펴보면 이미 개발된 애플리케이션이 배포되기 전에 테스트를 하기 위한 솔루션과 함께 개발시부터 최적화를 위한 솔루션 등이 출시되고 있다. 성능관리 솔루션은 성능 이슈의 발생원인 및 정확한 위치 등의 구체적인 진단 정보를 제공하며 개발자들간의 효과적인 의사소통 통로를 마련해 주고, 개발을 의뢰한 업체가 흔쾌히 받아들이도록 체계적이면서 명확한 피드백을 제공해 준다. 또 애플리케이션 테스팅에 소요되는 주기도 단축해 준다. 이러한 솔루션들은 개발자들이 개발 시부터 사용할 수 있도록 JBuilder, WSAD 등의 개발툴과 연동할 수 있도록 되어 있다. 이를 통해 작성되는 코드를 최적화해 추후에 개발 완료된 애플리케이션의 성능향상에 큰 도움을 준다.

J2EE 성능관리 제품군

J2EE 성능관리를 위한 솔루션들의 특징을 살펴보면 다음과 같다. 현재 시장에서는 자바의 프로파일러 인터페이스인 JVMPI(Java Virtual Machine Profiling Interface) 기반의 자바 애플케이션 성능관리 툴이 많이 출시되어 있다.

ej-technologies의 J프로파이러는 JVMPI 기반의 자바 애플리케이션 성능관리 툴로, J2EE 와 J2SE 애플리케이션 개발시 쓰레드를 체크해 디버깅하는 기능과 애플리케이션 손실 현상 및 CPU 메로리 소모량 측정, 버추얼머신(VM)의 원격 측정 리포트 기능 등을 제공한다.

Borland의 Optimizeit Suite도 JVMPI 기반의 툴로 3가지 성능관리를 위한 기능이 있는데 먼저 Code Coverage라는 기능은 개발자들이 각 클래스, 메소드, 각 코드 라인의 실행 빈도를 살펴볼 수 있게 해주며 쓸모없는 코드를 제거함으로써 애플리케이션 배포크기를 줄여준다. Profiler는 개발자들이 애플리케이션 속도와 신뢰도를 극대화하고 성능을 개선시킬 필요가 있는 크리티컬한 코드를 빠르게 분리해낼 수 있게 해 주고, Thread Debugger로 모호한 쓰레드 문제들을 해결하도록 도와 준다. 이 기능은 모든 쓰레드들의 상태를 보여주고 실시간으로 모니터링하여 치명적인 에러를 일으키는 쓰레드 고갈과 충돌을 피할 수 있으며, 데드락이 발생하기 전에 미리 예측할 수 있다. Borland Optimizeit ServerTrace는 J2EE 플랫폼에 애플리케이션을 배포하기 위한 준비 단계에서 테스팅 절차를 수행하므로, 애플리케이션의 성능 향상을 극대화시켜 준다. 성능 이슈의 발생원인 및 정확한 위치 등의 구체적인 진단 정보를 제공하며 JDBC, JSP, EJB, JNDI, RMI, JMS 등의 J2EE 스팩으로 개발한 애플리케이션을 진단하는데 편리한 기능을 제공한다.

Wily Technology의 Introscope는 운영팀을 대상으로 한 Web Application Server의 성능 관리를 위한 모니터링 툴로서 애플리케이션의 수행 속도와 횟수 및 SQL문에 대한 모니터링 기능을 제공한다. Agent 컴포넌트는 성능에 대한 정보를 수집/저장하고 Enterprise Manager 컴포넌트에서 비동기적인 정보를 기록한다. JSP, EJB, JDBC, 소켓 같은 J2EE 컴포넌트들을 관리하며 사용자는 그들 자신이 만들었거나 Custom Tracing 기능을 이용하여 원하는 클래스나 메소드를 관리할 수 있도록 소스 코드의 변경 없이 설정할 수 있다. 대부분 컴포넌트들을 모니터하기 위해 평균 응답시간과 초당 응답시간을 측정하고, 자바 시스템 접근에 대한 bytes per second와 CPU utilization의 기능을 포함하고, 컴포턴트 각각의 성능 정보를 제공한다. 이러한 성능 측정은 어떻게 애플리케이션 컴포넌트들이 로드시 수행되는지를 이해할 수 있게 한다.

최근 들어 비즈니스 고객과 엔드유저는 엔터프라이즈급 애플리케이션이 중단되지 않고, 24×7로 가용할 수 있어야 한다는 것을 더 요구하고 있다. 즉, 기업은 그들이 투자한 엔터프라이즈 애플리케이션들이 성능, 확장성, 그리고 신뢰성 측면에서 자신들이 요구하는 기준에 부합되길 바라고 있는 것이다. 이러한 요구에 부합하기 위해 개발자는 개발 초기부터 테스트와 개발을 병행해 가며 최적화를 한다면 최종적으로 개발 완료된 애플리케이션은 기업이 원하는 애플리케이션이 될 수 있을 것이다.

국내에서 개발자는 만능일 것을 요구받고 있다. 개발자가 요구분석, 디자인, 개발, 테스트까지 할 수 있기를 바라는 기업이 많기 때문이다. 이런 모든 부분을 수용할 수 있는 개발자라면 정말 대단한 사람일 것이다. 하지만 이러한 각각의 부분이 보다 전문성을 가지고 있는 직업군으로 나뉘어 지는 것이 바람직하다. 요즘 국내에서도 성능관리를 위한 관리자(성능관리자)가 생겨나고 있는 추세다. 보다 효율적인 애플리케이션 개발을 위해서 개발자는 개발에 집중하고 성능관리자는 애플리케이션의 문제점을 테스트해서 개발자에게 개선되어야 하는 부분을 가르쳐 준다면 최적의 성능의 애플리케이션을 개발할 수 있다는 점에서 매우 긍정적인 현상이라고 볼 수 있다.

Posted by foryamu
,

spring ldap

java/spring 2007. 7. 26. 09:52

Simplify directory access with Spring LDAP

Try a Spring-based approach to LDAP programming with JNDI

Spring LDAP is a Spring-based framework that simplifies LDAP programming on the Java platform. In this step-by-step guide to using Spring LDAP you will learn how the framework handles the low-level coding required by most LDAP clients, so that you can focus on developing your application's business logic. You will also practice simple CRUD operations using Spring LDAP and learn about more advanced operations such as creating dynamic filters and converting LDAP entries into Java beans.

The Lightweight Directory Access Protocol is an essential component of most large-scale enterprise application deployments today. LDAP is primarily used to store information related to user identity, such as a user's username, password, and e-mail address. It is also used in security implementations where it is necessary to store user access rights for authentication and authorization purposes.

Java Naming and Directory Interface (JDNI) is the API used for LDAP programming on the Java platform. It defines a standard interface that can be used within your application to interact with any LDAP server. Unfortunately, using JNDI typically entails writing a lot of low-level, repetitive code. JNDI makes far too much work of simple procedures, such as ensuring that resources have been properly opened and closed. In addition, most JNDI methods throw checked exceptions, which are time-consuming to handle. Upon close inspection, it seems that 50 to 60 percent of the time spent programming JNDI is wasted on handling repetitive tasks.

Spring LDAP is an open source Java library designed to simplify LDAP programming on the Java platform. Just as the Spring Framework takes much of the low-level programming out of Java enterprise application development, Spring LDAP frees you from the infrastructural details of using LDAP. Rather than worrying about NamingExceptions and getting InitialContexts, you are free to concentrate on your application's business logic. Spring LDAP also defines a comprehensive unchecked exception hierarchy and provides helper classes for building LDAP filters and distinguished names.

Spring LDAP and JNDI

Note that the Spring LDAP framework does not replace JNDI. Rather, it provides wrapper and utility classes over JNDI to simplify LDAP programming on the Java platform.

In this article, a beginner's guide to using Spring LDAP, I will start by developing a simple JNDI program for executing an LDAP search. I'll then demonstrate how much easier it is to do the same thing using the Spring LDAP framework. I'll show you how to use Spring LDAP's AttributeMappers to map LDAP attributes to Java beans, and how to use its dynamic filters to build queries. Finally, I'll provide a step-by-step introduction to using the Spring LDAP framework to add, delete and modify data in your LDAP server.

Note that this article assumes you are familiar with the concepts and terminology of the Spring Framework. See the Resources section to learn more about the Spring Framework, LDAP and JNDI as well as to download the sample application.

A simple JNDI client

Listing 1 shows a simple JNDI program that will print out the cn attributes of all the Person type objects on your console.

Listing 1. SimpleLDAPClient.java

public class SimpleLDAPClient {
public static void main(String[] args) {
Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:10389/ou=system");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
env.put(Context.SECURITY_CREDENTIALS, "secret");
DirContext ctx = null;
NamingEnumeration results = null;
try {
ctx = new InitialDirContext(env);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
results = ctx.search("", "(objectclass=person)", controls);
while (results.hasMore()) {
SearchResult searchResult = (SearchResult) results.next();
Attributes attributes = searchResult.getAttributes();
Attribute attr = attributes.get("cn");
String cn = (String) attr.get();
System.out.println(" Person Common Name = " + cn);
}
} catch (NamingException e) {
throw new RuntimeException(e);
} finally {
if (results != null) {
try {
results.close();
} catch (Exception e) {
}
}
if (ctx != null) {
try {
ctx.close();
} catch (Exception e) {
}
}
}
}
}

The first thing I've done in Listing 1 is to create an InitialDirContext object, which is then used as the context for the following directory operations. When creating a new Context object I configure properties such as the username, password and authentication mechanism that can be used to connect to the LDAP server. I've managed this by creating a Hashtable object, setting up all these properties as key/value pairs in the Hashtable and passing the Hashtable to the InitialDirContext constructor.

The immediate problem with this approach is that I've hard-coded all the configuration parameters into a .java file. This works fine for my example, but not for a real-world application. In a real-world application I would want to store the connection properties in a jndi.properties file and place that file in either my project's classpath or its <JAVA_HOME>/lib folder. Upon creation of a new InitialDirContext object, the JNDI API would look in both of those places for the jndi.properties file, then use it to create a connection to the LDAP server.

JNDI configuration parameters

Listing 2 shows the JNDI configuration parameters for connecting to my LDAP server. I explain the meaning of the parameters below.

Listing 2. JNDI configuration parameters for LDAP

java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
java.naming.provider.url=ldap://localhost:10389/ou=system
java.naming.security.authentication=simple
java.naming.security.principal=uid=admin,ou=system
java.naming.security.credentials=secret
  1. Context.INITIAL_CONTEXT_FACTORY (java.naming.factory.initial) should be equal to the fully qualified class name that will be used to create a new initial context. If no value is specified then the NoInitialContextException is thrown.
  2. Context.PROVIDER_URL (java.naming.provider.url) should be equal to the URL of the LDAP server that you want to connect to. It should be in the format ldap://<hostname>:<port>.
  3. Context.SECURITY_AUTHENTICATION (java.naming.security.authentication) represents the type of authentication mechanism you want to use. I've used a username and password for authentication in my example, so the value of this property is simple.
  4. Context.SECURITY_PRINCIPAL (java.naming.security.principal) represents the distinguished username (DN) that should be used to establish a connection.
  5. Context.SECURITY_CREDENTIALS (java.naming.security.credentials) represents the user's password.

The JNDI client code

After getting the Context object my next step is to create a SearchControl object, which encapsulates the factors that determine the scope of my search and what will be returned. I want to search the entire subtree rooted at the context, so I set the search scope to SUBTREE_SCOPE by calling the setSearchScope() method of SearchControl, as previously shown in Listing 1.

Next, I call the search() method of DirContext, passing in (objectclass=person) as the value of the filter. The search() method will return a NamingEnumeration object containing all the entries in the subtree of Context, where objectclass is equal to person. After getting a NamingEnumeration as my result object, I iterate through it and print a cn attribute for each Person object.

That completes my explanation of the JNDI client code. Looking at SimpleLDAPClient.java, shown in Listing 1, you can easily see that more than half of the code goes toward opening and closing resources. Another problem with the JNDI API is that most of its methods will throw a NamingException or one of its subclasses in the case of an error. Because NamingException is a checked exception, you must handle it if it is thrown, but can you really recover from an exception if your LDAP server is down? No, you can't.

Most developers get around JNDI NamingExceptions by simply catching them and doing nothing. The trouble with this solution is that it can cause you to lose important information.


Simplify directory access with Spring LDAP

Try a Spring-based approach to LDAP programming with JNDI

Page 2 of 4

Getting started with Spring LDAP

Next, we'll see what happens when we write the same simple search client using Spring LDAP. I'll start by showing you how to set up the Spring LDAP development environment, so that you can follow along with the example.

You will need both the Spring Framework binaries and the Spring LDAP binaries to run the following example. Spring LDAP requires J2SE 1.4 and is compatible with Spring Framework versions 1.2.8 and 2.0. The sample code in this article is based on the 1.1.2 version of Spring LDAP and has been tested using the Spring Framework 2.0.1.

Set up the Spring LDAP development environment as follows:

  1. If you are using an IDE like Eclipse, then create a Java project and name it "SpringLDAPFramework" (or something similar). If you prefer not to use an IDE then simply download the sample code for this article. The sample code contains an Ant build script that you can use to build the example.
  2. Unzip the downloaded springbinaries file into a folder, such as c:/temp. Add the c:/temp/spring-framework-2.0.1/dist/spring.jar to the classpath of your SpringLDAPFramework project.
  3. Likewise, unzip the springldapframework binaries into a folder such as c:/temp, then add C:/temp/spring-ldap-1.1.2/dist/spring-ldap-1.1.2.jar to the classpath of your SpringLDAPFramework project.

Your Spring LDAP development environment is set up, and you are ready to start building a simple Spring LDAP application.

The ContactDAO interface

At heart the Spring Framework is a dependency injection framework, which means that it supports programming to interfaces rather than programming to classes. Spring LDAP takes a similar approach to directory programming. The first step to creating a Spring LDAP client is, therefore, to create a ContactDAO interface that defines all the LDAP operations we want to perform. ContactDAO is shown in Listing 3.

Listing 3. The ContactDAO interface

public interface ContactDAO {

public List getAllContactNames();

public List getContactDetails(String commonName, String lastName);

public void insertContact(ContactDTO contactDTO);

public void updateContact(ContactDTO contactDTO);

public void deleteContact(ContactDTO contactDTO);
}

Next, we'll create an LDAPContactDAO.java class that implements the ContactDAO interface. For now, we'll implement just one method in this class. The getAllContactNames() method searches all objects where objectclass is equal to person and returns each object's cn in the form of a list. getAllContactNames() is shown in Listing 4.

Listing 4. The getAllContactNames() method

public class LDAPContactDAO implements ContactDAO{
private LdapTemplate ldapTemplate;
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}

public List getAllContactNames() {
return ldapTemplate.search("", "(objectclass=person)",
new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs)
throws NamingException {
return attrs.get("cn").get();
}
});
}

}

Now, note the ldapTemplate property and setter method in Listing 4. setLdapTemplate() is used to inject an LdapTemplate object into the LDAPContactDAO class. All the getAllContactNames() method does is to call the search() method of the LdapTemplate class -- which is enough to get the job done. I'll explain all this in detail later in the discussion.

Working with the Spring context

Our next step is to create a Spring context file for the Spring LDAP example, as shown in Listing 5. (See the Resources section to learn more about the Spring context file.)

Listing 5. springldap.xml

<beans>
<bean id="contextSource"
class="org.springframework.ldap.support.LdapContextSource">
<property name="url" value="ldap://localhost:10389" />
<property name="base" value="ou=system" />
<property name="userName" value="uid=admin,ou=system" />
<property name="password" value="secret" />
<property name="pool" value="true"/>
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>
<bean id="ldapContact"
class="com.javaworld.sample.LDAPContactDAO">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
</beans>

In this example we are using an instance of the LdapContextSource bean to manage the connection to our LDAP server. Note that you should use the DirContextSource bean if you want to connect to an LDAP server with LDAPv2 compatibility. The LdapContextSource bean has several properties that you can use to configure your LDAP connection, as follows:

  • url should be equal to the LDAP server URL in the format of ldap://<hostname>:<port>. In this example we want to connect to an Apache Directory Server that is installed on a local machine. By default, Apache DS listens on port 10389, so the value of the url property is ldap://localhost:10389. If you want to connect to an LDAP server on a secured port, use ldaps://<hostname>:<securedport> instead.
  • base is an optional property used to specify the value of the base suffix. Once the base suffix is set, all LDAP operations start at this base level. For the purposes of this example all LDAP operations are set to start at the base level of "ou=system".
  • userName is the username that should be used for getting an authenticated context. The username should be the distinguished name (DN) of the user.
  • password is the user password that should be used for getting an authentication context.
  • pool is a boolean flag indicating that you want to pool LDAP connections in your application. By default, instances of Context and NamingEnumerations that are derived from Context will share the same connection. This will change if you modify one of the Context instances so that sharing is impossible -- for example by specifying new security credentials. In addition, you can set the value of the com.sun.jndi.ldap.connect.pool environment variable to true while opening an LDAP connection. In that case JDNI will create a pool of LDAP connections. Whenever you want to use a connection, JNDI will return the connection from your pool. Once you are done with it, it will go back into the pool. Specifying the value of the pool property to true in your LdapContextSource configuration will set the com.sun.jndi.ldap.connect.pool property equal to true on the underlying LDAP connection.

Authentication in Spring LDAP

We've set up the sample application so that we can get authenticated access to our LDAP server, which will allow us to perform both read and write operations. Note that some LDAP servers provide read-only anonymous access, which allows you to connect to the server without user credentials and perform read operations. You can get an anonymous read-only connection by setting the server's anonymousReadOnly property to true.

In Spring LDAP there are two ways to manage authentication. If you specify static authentication information (username and password ) in your LdapContextSource bean definition, the Spring LDAP framework will use these credentials to open all LDAP connections. Another approach is to specify user credentials dynamically. For instance, if you were using Acegi Security System to manage the security of your Web application you could define Acegi's AuthenticationSource bean in your LDAP context file. Thereafter, whenever the Spring LDAP framework wanted to open a new LDAP connection it would pass control to the AuthenticationSource object and get the username and password required to open the current connection. (See the Resources section to learn more about dynamic authentication with Acegi Security System.)

The LdapContextSource also takes a urls property that can be used when you have multiple alternate LDAP servers. In that case it will try connecting to each LDAP server one after another until it is able to connect to a server successfully. Note that this feature is implemented at the level of JNDI and not at the level of the Spring LDAP framework. All the Spring LDAP framework does is take list of the LDAP URLs, concatenate them in a string separated with spaces, and set it as a value of Context.PROVIDER_URL.



The Spring LDAP client

Finally, the complete Spring LDAP client is shown in Listing 6.

Listing 6. SpringFrameworkLDAPClient.java

public class SpringFrameworkLDAPClient {

public static void main(String[] args) {
try {
Resource resource = new ClassPathResource("com/javaworld/sample/springldap.xml");
BeanFactory factory = new XmlBeanFactory(resource);
ContactDAO ldapContact = (LDAPContactDAO)factory.getBean("ldapContact");
List contactList = ldapContact.getAllContactNames();
for( int i = 0 ; i < contactList.size(); i++){
System.out.println("Contact Name " + contactList.get(i));
}
} catch (DataAccessException e) {
System.out.println("Error occured " + e.getCause());
}
}
}

In the above class we first initialize the Spring Framework by creating a BeanFactory object. Next, we ask the Spring Framework for an LDAPContactDAO, and upon getting the object call its getAllContactNames() method. This method returns a list reporting the cn of all the Person type objects in the LDAP server. Finally, we iterate through the list and print it on the console.

If you execute the SpringFrameworkLDAPClient, it should return output similar to that of the JNDI-based SimpleLDAPClient. In this case, however, the results are achieved with far less code, which is also more manageable.

Now you've seen the difference between a simple JNDI program for interacting with an LDAP server and the same program implemented using the Spring LDAP framework. In the next sections I'll further explain search queries using Spring LDAP, then get into some more advanced uses of the framework, such as attribute mapping and creating dynamic filters. I'll conclude with an introduction to CRUD operations using Spring LDAP.

Search queries under the hood

At the beginning of this article I briefly talked about the steps required to execute simple search queries using Spring LDAP. A deeper understanding of search queries will be useful later, when you are using Spring LDAP for create, read, update and delete operations on your LDAP server. When you make a call on the search() method of the LdapTemplate class, the resulting sequence of events is as follows:

  1. The LdapTemplate class calls the LdapContextSource class's getReadOnlyContext() method. The getReadOnlyContext() method checks the value of the anonymousReadOnly property. If the value is true, an anonymous connection is opened. Otherwise a connection is opened using the credentials supplied in your LDAP context file.
  2. The Spring LDAP framework class defines a DirContextProcessor interface that you can implement if you want to execute some business logic either before or after a search. DirContextProcessor defines two methods: preProcess(), which gets called before a search, and postProcess(), which gets called after a search. We haven't implemented the DirContextProcessor interface for this simple example, so the Spring LDAP framework will use NullContextProcessor, the default do-nothing implementation of the DirContextProcessor interface.
  3. The LdapTemplate next calls the executeSearch() method of the SearchExecutor object. SearchExecutor performs the actual LDAP search and returns the result.
  4. Once results are returned, LdapTemplate iterates through the results passing control to an instance of the AttributesMapper class for every result. The mapFromAttributes() method of the AttributesMapper class gives you a chance to convert LDAP attributes into custom Java beans. Once control is returned from the mapFromAttributes() method, the LdapTemplate class takes the result and adds them to a List.
  5. The LdapTemplate class calls the postProcess() method of DirContextProcessor class, giving you the opportunity to execute custom business logic.
  6. The LdapTemplate class takes care of closing resources in this last step. First it will call a closeNamingEnumeration() method to close NamingEnumeration objects returned by the search() method. Next, it will call a closeContext() method, which will close the connection to your LDAP server by calling the close() method of the underlying Context object.

Converting LDAP entries into custom Java beans

Spring LDAP uses an AttributesMapper class to convert LDAP entries into Java beans. In this section I'll show you how to extend the Spring LDAP client to create a ContactDTO object for every Person type object returned by an LDAP search. The first step is to create a simple Java bean with three attributes: commonName, lastName, and description, as shown in Listing 7.

Listing 7. ContactDTO.java

public class ContactDTO {
String commonName;
String lastName;
String description;

public String getCommonName() {
return commonName;
}
public void setCommonName(String commonName) {
this.commonName = commonName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

Next, we create ContactAttributeMapper.java, as shown in Listing 8.

Listing 8. ContactAttributeMapper.java

public class ContactAttributeMapper implements AttributesMapper{

public Object mapFromAttributes(Attributes attributes) throws NamingException {
ContactDTO contactDTO = new ContactDTO();
String commonName = (String)attributes.get("cn").get();
if(commonName != null)
contactDTO.setCommonName(commonName);
String lastName = (String)attributes.get("sn").get();
if(lastName != null)
contactDTO.setLastName(lastName);
Attribute description = attributes.get("description");
if(description != null)
contactDTO.setDescription((String)description.get());
return contactDTO;
}

}

The ContactAttributeMapper class implements AttributesMapper, a simple interface used by Spring LDAP to convert LDAP attributes to custom Java beans. Upon getting results from an LDAP server the Spring LDAP framework iterates through the results and will call a ContactAttributeMapper.mapFromAttributes() method for each object returned by the server to convert attributes into custom Java beans.

In Listings 7 and 8 above, we created a new instance of ContactDTO inside the mapFromAttributes method. We then mapped the value of the cn attribute to commonName; the value of the sn attribute to lastName; and the value of the description attribute to the description property of ContactDTO. The mapFromAttributes() method returns this newly created object of ContactDTO and the Spring LDAP framework adds it to LinkedList, which is the return value of the LdapTemplate.search() method.

Remember to change the getAllPersonNames() method so that it passes a ContactAttributeMapper instead of a generic class, as shown in Listing 9.

Listing 9. getAllPersonNames() passes ContactAttributeMapper

public List getAllPersonNames() {
return ldapTemplate.search("", "(objectclass=person)",new ContactAttributeMapper());
}

Building dynamic filters

Until now we have used the Spring LDAP framework to execute generic queries, but in most cases you will want to execute more specific queries. Spring LDAP defines multiple classes in the org.springframework.ldap.support.filter package that facilitate the creation of dynamic filters. In Listing 10, we extend the Spring LDAP client with the getContactDetail() method, which takes firstName and lastName as arguments and returns all Person records for a given first name and last name.

Listing 10. getContactDetail()

public List getContactDetails(String firstName,String lastName){
AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter("objectclass","person"));
andFilter.and(new EqualsFilter("cn",firstName));
andFilter.and(new EqualsFilter("sn",lastName));
System.out.println("LDAP Query " + andFilter.encode());
return ldapTemplate.search("", andFilter.encode(),new ContactAttributeMapper());

}

In the above code we have specified that we want to search for contacts that meet three conditions: (1) the record must be of type person; (2) the value of the cn attribute should be equal to firstName; and (3) the value of sn should be equal to lastName. The LDAP query for this search would be

(&(objectclass=person)(cn=<firstName>)(sn=<lastName>))

The first step to creating a dynamic filter is to create an instance of AndFilter. Next, we set the filter conditions using Spring LDAP's filter classes, as follows:

  • EqualFilter("objectclass","person") ensures that objectclass is equal to person.
  • EqualsFilter("cn",firstName) ensures that only person objects whose cn attribute value is equal to firstName will be returned.
  • EqualsFilter("sn",lastName) ensures that only person objects whose sn attribute value is equal to lastName will be returned.
  • AndFilter() ensures that only those person records that meet all three requirements will be returned.

Once you have set the conditions you can retrieve the actual LDAP query generated by calling Filter.endcode(). This method will return a string representing your custom dynamic filter. Pass this filter object to LdapTemplate.search() and it will return only entries matching all three conditions. See the Resources section to learn more about creating dynamic filters.



Simplify directory access with Spring LDAP

Try a Spring-based approach to LDAP programming with JNDI

Page 4 of 4

Using Spring LDAP for CRUD operations

In some cases you will need to do more than read or search records in your LDAP server. You will also need to add records, modify them, and delete them. In this final section I'll show you how to perform these simple CRUD operations using Spring LDAP. Listing 11 updates LDAPContactDAO.java (shown in Listing 4) with a new method for adding records to your LDAP server.

Listing 11. Add insertContact() to LDAPContactDAO.java

public void insertContact(ContactDTO contactDTO) {
Attributes personAttributes = new BasicAttributes();
BasicAttribute personBasicAttribute = new BasicAttribute("objectclass");
personBasicAttribute.add("person");
personAttributes.put(personBasicAttribute);
personAttributes.put("cn", contactDTO.getCommonName());
personAttributes.put("sn", contactDTO.getLastName());
personAttributes.put("description", contactDTO.getDescription());
DistinguishedName newContactDN = new DistinguishedName("ou=users");
newContactDN.add("cn", contactDTO.getCommonName());
ldapTemplate.bind(newContactDN, null, personAttributes);
}
Performance note
LDAP directory servers are intended primarily for read-only access and do not perform well if you frequently modify the data they contain. Keep CRUD operations to a minimum in your LDAP server. Otherwise, if your design requires extensive data modification, you may want to consider using an RDBMS, which is better suited to write operations than an LDAP directory server.

The insertContact() method takes an object of the ContactDTO type and adds it as a person type object in the LDAP server. The first thing that we do in this method is create a new instance of the BasicAttribute class where objectclass is equal to person. We then set commonName as the value of the cn attribute; lastName as the value of the sn attribute; and description as the value of the description attribute. Once we are done, we pass ContactDTO to the LdapTemplate.bind() method, along with the DistinguishedName object. DistinguishedName is a utility class defined in the Spring LDAP framework that implements the Name interface.

The first thing the bind() method does is to read your username and password information and use it to open a connection to the LDAP server. Note that you cannot use anonymous access for write operations, so in this case you must supply your username and password. Once your access permission has been established the bind() method is called and the record is added to the server.

Modifying data

Listing 12 shows how to modify data in your LDAP server by adding a method to LDAPContactDAO.java.

Listing 12. Add updateContact() to LDAPContactDAO.java

public void updateContact(ContactDTO contactDTO) {
Attributes personAttributes = new BasicAttributes();
BasicAttribute personBasicAttribute = new BasicAttribute("objectclass");
personBasicAttribute.add("person");
personAttributes.put(personBasicAttribute);
personAttributes.put("cn", contactDTO.getCommonName());
personAttributes.put("sn", contactDTO.getLastName());
personAttributes.put("description", contactDTO.getDescription());
DistinguishedName newContactDN = new DistinguishedName("ou=users");
newContactDN.add("cn", contactDTO.getCommonName());
ldapTemplate.rebind(newContactDN, null, personAttributes);
}

You may have noticed that the updateContact() method is very similar to insertContact(). The only difference between the methods is that at the end of updateContact() you call LdapTemplate.rebind() instead of bind(). The difference between bind() and rebind() is that an NameAlreadyBoundException will be thrown if you try to bind an attribute with the same name as an existing attribute. In the case of a rebind() the new object would overwrite the existing entry.

Deleting data

if you want to delete data in your LDAP server you can do so by simply adding a delete method to LDAPContactDAO.java, as shown in Listing 13.

Listing 13. Add deleteContact() to LDAPContactDAO.java

public void deleteContact(ContactDTO contactDTO) {
DistinguishedName newContactDN = new DistinguishedName("ou=users");
newContactDN.add("cn", contactDTO.getCommonName());
ldapTemplate.unbind(newContactDN);
}

All that is needed to delete a contact in an LDAP server is to pass a DistinguishedName type object pointing to the record you want to delete to the LdapTemplate unbind() method. The LdapTemplate will use this object and call the unbind() operation on the underlying Context object.

Summary

In this article you've learned how the Spring LDAP framework minimizes the low-level coding typically associated with LDAP clients. The Spring LDAP framework does not replace JNDI but rather wraps and extends it to simplify LDAP programming on the Java platform. In addition to building a simple Spring LDAP client and performing basic CRUD operations, you've learned how to set up dynamic filters and convert LDAP entries to Java beans. See the Resources section to learn more about the Spring LDAP framework and download the sample application that comes with this article.

Author Bio

Sunil Patil is a Java Enterprise/Portlet developer working for Ascendant Technology in San Francisco, California. He is the author of Java Portlets 101 (SourceBeat, April 2007) and has written numerous articles published by O'Reilly Media. Sunil was a member of IBM's WebSphere Portal Server development team for three years and is actively involved in the Pluto community. In addition to being an IBM Certified WebSphere Portal Server Application Developer for both v5.0 and v5.1, he is a Sun Microsystems Certified Java Programmer, a Web component developer, and a business component developer. You can view Sunil's blog at http://jroller.com/page/SunilPatil.


Posted by foryamu
,