Mapeamento de DN do LDAP com POJOS usando Spring

Em um post anterior eu introduzi o ApacheDS que é uma excelente solução para LDAP. Agora vou mostrar como é possível mapear atributos do LDAP de forma automática usando anotações. Para você realizar isso é necessário todos os requisitos do exemplo anterior.

Vou utilizar a mesma arvore LDAP do post anterior. Muito bem vamos aos códigos! Vamos começar por um simples POJO chamado Pessoa.




package com.blogspot.diegopacheco.ldap.pojo;

import com.blogspot.diegopacheco.ldap.bind.LdapBinding;

/**
* Pojo Pessoa.
*
* @author Diego Pacheco
* @since 07/12/2008
*/
public class Pessoa {

@LdapBinding(dn="uid")
private String nome;

@LdapBinding(dn="cn")
private String cargo;


public Pessoa() {}

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}

public String getCargo() {
return cargo;
}
public void setCargo(String cargo) {
this.cargo = cargo;
}

@Override
public String toString() {
return "nome: " + nome + ", cargo: " + cargo;
}   

}

Esse POJO que vocês estão vendo é o mesmo do post anterior com a diferença que agora eu criei uma anotação para mapeamento automático do LDAP. A anotação @LdapBinding tem dois parâmetros dn no qual é o nome da entrada no LDAP e o outro atributo que é opcional é uma class do tipo AttributeConverter.

Se você criar uma classe que implementa AttributeConverter e passar por parâmetro para anotação a minha engine de binging irá chamar seu conversor na hora de processar esse campo, esse recurso é interessante pois você pode precisar colocar algum código customizado de validação, log ou até mesmo a própria conversão :)

Agora vamos olha a anotação.

package com.blogspot.diegopacheco.ldap.bind;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Anotação para realizar binding de um pojo para uma
* query do SpringLDAP.
*
* @author Diego Pacheco
* @since 08/12/2008
* @version 1.0
*
*/
@Retention(RetentionPolicy.RUNTIME)     
@Target(ElementType.FIELD)
@Documented
@SuppressWarnings("unchecked")
public @interface LdapBinding{
String dn();       
Class ac() default LdapBinding.class;   
}

Como já dito antes o campo ac é opcional caso você não informe valor para esse atributo da anotação o valor default será a própria classe da anotação, quando a engine de binging for processar esse cara ela irá ignorar a conversão neste caso.

Para criar o seu conversor basta implementar essa interface a baixo.

package com.blogspot.diegopacheco.ldap.bind.converter;

import javax.naming.directory.Attributes;

/**
* Interface para a conversão de tipos.
*
* @author Diego Pacheco
* @since 08/12/2008
* @version 1.0
*/
public interface AttributeConverter {

/**
* Método para a conversão de atributos.
* @param atts instancia de Attributes
* @return valor convertido
*/
public Object convert(Attributes atts);

}

Como você pode perceber a interface de conversão é simples porem ao mesmo tempo muito flexível. Seguindo o nosso baile vamos a classe que faz o papel de engine de Binging, é aqui que a mágica acontece :)


package com.blogspot.diegopacheco.ldap.bind;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.naming.directory.Attributes;

import com.blogspot.diegopacheco.ldap.bind.converter.AttributeConverter;

/**
* Engine responsável por fazer binding de um POJO apartir
* de uma query do SpringLDAP.
*
* @author Diego Pacheco
* @since 08/12/2008
* @version 1.0
*
*/
public class SpringLdapAnnotationBinder{

/**
* Metodo que realiza o binding
* @param pojo Class do pojo
* @param attrs Attributs retormados da query
* @return Object
*/
@SuppressWarnings("unchecked")
public static Object bind(Class pojo,Attributes attrs){
return doBind(pojo, attrs, null);
}

/**
* Metodo que realiza o binding
* @param pojo Class do pojo
* @param attrs Attributs retormados da query
* @param ac AttributeConverter para o tipo apropriado
* @return Object
*/
@SuppressWarnings("unchecked")
public static Object bind(Class pojo,Attributes attrs,AttributeConverter ac){
return doBind(pojo, attrs, ac);
}

/**
* Metodo que realiza o binding de fato.
* @param pojo Class do pojo
* @param attrs Attributs retormados da query
* @param ac AttributeConverter para o tipo apropriado
* @return Object
*/
@SuppressWarnings("unchecked")
private static Object doBind(Class pojo,Attributes attrs,AttributeConverter ac){

//
// Para melhorar a performance você pode utilizar o EHCache ou um simles Map
// realizando cache da criação dos objetos.
//
Object inst = null;       
try {
inst = pojo.newInstance();    

// Percore todas as acontações do pojo que estejam anotadas com LdapBinding.
for(Field f: pojo.getDeclaredFields()){
if ((f.getAnnotation(LdapBinding.class))!=null){                    
LdapBinding lb = (LdapBinding)f.getAnnotation(LdapBinding.class);

// Obetm um conversor para esse atributo caso exista.
AttributeConverter  attributeConverter = (lb.ac().equals(LdapBinding.class)) ? null : (AttributeConverter)lb.ac().newInstance();

Object attValue = attrs.get(lb.dn()).get();   
if (attValue==null || "".equals(attValue)){
// log caso você queira!
}else{
//
// Aqui você pode usar cache também.
//
for (Method m :inst.getClass().getMethods()){
if (("set" + normalize(f.getName())).equals(m.getName())){
m.invoke(inst,((attributeConverter==null) ? attValue : attributeConverter.convert(attrs)));
break;
}                                
}                                                
}

}
}
return inst;

} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

private static String normalize(String str){
return str.substring(0,1).toUpperCase() + str.substring(1);
}

}

De cara já lhe digo que se você quizer utilizar meu código sinta-se a vontade, mas como essa classe realiza muitas operações com reflecção é autamente recomndado que você coloque cache, pode ser um cache simples mesmo com um hasmap por exemplo.

O método doBind faz tudo acontecer. Básicamente ele instancia o pojo e procura por todos os campos do pojo. Em uma versão melhorada você pode criar a opção de receber um pojo já instánciado tembém.

Voltando ao método doBind() ele verifica pela anotação LdapBinding, se o campo possui-la ele descobre se deve utilizar algum conversor para esse campo. Após isso o valor é recuperado da query e é iniciada uma pesquisa para o método *setNomeDoCampoNoPojo* para setar o valor.

Logo significa que você deve ter os getters e setters para os campos do POJO onde você utilizou a anotação de bind que criei. E finalmente o setter é executado e a operação recomeça para os outros campos do pojo.

O XML com as definições de beans do Spring fica assim:



            

            









Para testar eu criei a seguinte classe, utilizando JUnit.
package com.blogspot.diegopacheco.ldap.bind.test;

import java.util.List;

import javax.naming.NamingException;
import javax.naming.directory.Attributes;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.util.Assert;

import com.blogspot.diegopacheco.ldap.bind.SpringLdapAnnotationBinder;
import com.blogspot.diegopacheco.ldap.pojo.Pessoa;

/**
* Classe de testes
* @author Diego Pacheco
* @since 08/12/2008
*/
@SuppressWarnings("unchecked")
public class SpringLdapAnnotationBinderTest {

private ApplicationContext ac;

@Before
public void setUp(){
ac = new ClassPathXmlApplicationContext("classpath:SpringLDAP-Beans.xml");
Assert.notNull(ac,"Contexto do Sping não pode ser null!");
}   

@Test
public void testFindPessoa(){       
LdapTemplate ldapTemplate = (LdapTemplate)ac.getBean("ldapTemplate");
Assert.notNull(ldapTemplate,"LdapTemplate não pode ser null!");

List result = ldapTemplate.search("", "(objectclass=person)",2,new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs) throws NamingException {               
return SpringLdapAnnotationBinder.bind(Pessoa.class, attrs);                       
}
});
System.out.println(result);   
}

}
Basta utilizar o LdapTemplate do Spring e no AttributesMapper chamar o binding assim: SpringLdapAnnotationBinder.bind(POJO.class, atributos); E pronto já temos um mecanismo eficiente e simples para desenvolvimento com POJOS e LDAP. Os fontes completos estão disponíveis aqui. Espero que tenham gostado e até a próxima.

Popular posts from this blog

Telemetry and Microservices part2

Installing and Running ntop 2 on Amazon Linux OS

Fun with Apache Kafka