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!"); ListBasta 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.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); } }