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.
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.
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.
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 :)
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:
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.