Implementar conversores para entidades com Java Generics

Eu estou trabalhando no projeto JSF com Spring e Hibernate que, entre outras coisas, tem vários Converter que seguem o mesmo padrão:

  • getAsObject recebe a representação de string do id do object, converte-o para um número e busca a entidade do tipo dado e o id fornecido

  • getAsString recebe e entidade e retorna o id do object convertido em String

O código é essencialmente o seguinte (verificações omitidas):

 @ManagedBean(name="myConverter") @SessionScoped public class MyConverter implements Converter { private MyService myService; /* ... */ @Override public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) { int id = Integer.parseInt(value); return myService.getById(id); } @Override public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) { return ((MyEntity)value).getId().toString(); } } 

Dado o grande número de Converter que são exatamente assim (exceto para o tipo de MyService e MyEntity claro), eu queria saber se valeria a pena usar um único conversor genérico. A implementação do genérico por si só não é difícil, mas não tenho certeza sobre a abordagem correta para declarar o Beans.

Uma solução possível é a seguinte:

1 – Escreva a implementação genérica, vamos chamá-la MyGenericConverter , sem qualquer anotação de Bean

2 – Escreva o anúncio conversor específico de uma subclass de MyGenericConverter e anote-o conforme necessário:

 @ManagedBean(name="myFooConverter") @SessionScoped public class MyFooConverter implements MyGenericConverter { /* ... */ } 

Enquanto escrevia isso, percebi que talvez um Generic não fosse realmente necessário, então talvez eu pudesse simplesmente escrever uma class base com a implementação dos dois methods e subclass conforme necessário.

Há alguns detalhes não triviais que precisam ser resolvidos (como o fato de que eu teria que abstrair a class MyService alguma forma), então minha primeira pergunta é: vale a pena o aborrecimento?

E se assim for, existem outras abordagens?

O mais fácil seria permitir que todas as suas entidades JPA se estendam a partir de uma entidade de base como esta:

 public abstract class BaseEntity implements Serializable { private static final long serialVersionUID = 1L; public abstract T getId(); public abstract void setId(T id); @Override public int hashCode() { return (getId() != null) ? (getClass().getSimpleName().hashCode() + getId().hashCode()) : super.hashCode(); } @Override public boolean equals(Object other) { return (other != null && getId() != null && other.getClass().isAssignableFrom(getClass()) && getClass().isAssignableFrom(other.getClass())) ? getId().equals(((BaseEntity) other).getId()) : (other == this); } @Override public String toString() { return String.format("%s[id=%d]", getClass().getSimpleName(), getId()); } } 

Observe que é importante ter um equals() (e hashCode() ) adequado, caso contrário, você enfrentará o erro de validação: O valor não é válido . Os testes da Class#isAssignableFrom() devem evitar a falha de testes em proxies baseados no Hibernate, sem a necessidade de recorrer ao método auxiliar Hibernate#getClass(Object) específico do Hibernate#getClass(Object) .

E ter um serviço de base como este (sim, eu estou ignorando o fato de que você está usando o Spring; é só para dar a ideia base):

 @Stateless public class BaseService { @PersistenceContext private EntityManager em; public BaseEntity find(Class> type, Number id) { return em.find(type, id); } } 

E implemente o conversor da seguinte forma:

 @ManagedBean @ApplicationScoped @SuppressWarnings({ "rawtypes", "unchecked" }) // We don't care about BaseEntity's actual type here. public class BaseEntityConverter implements Converter { @EJB private BaseService baseService; @Override public String getAsString(FacesContext context, UIComponent component, Object value) { if (value == null) { return ""; } if (modelValue instanceof BaseEntity) { Number id = ((BaseEntity) modelValue).getId(); return (id != null) ? id.toString() : null; } else { throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e); } } @Override public Object getAsObject(FacesContext context, UIComponent component, String value) { if (value == null || value.isEmpty()) { return null; } try { Class type = component.getValueExpression("value").getType(context.getELContext()); return baseService.find((Class>) type, Long.valueOf(submittedValue)); } catch (NumberFormatException e) { throw new ConverterException(new FacesMessage(String.format("%s is not a valid ID of BaseEntity", submittedValue)), e); } } } 

Observe que está registrado como @ManagedBean vez de @FacesConverter . Este truque permite que você injete um serviço no conversor através de, por exemplo, @EJB . Veja também Como injetar @EJB, @PersistenceContext, @Inject, @Autowired, etc em @FacesConverter? Então você precisa referenciá-lo como converter="#{baseEntityConverter}" vez de converter="baseEntityConverter" .

Se acontecer de você usar esse conversor com mais frequência para os componentes UISelectMany / UISelectMany ( e amigos), você poderá achar o OmniFaces SelectItemsConverter muito mais útil. Ele converte com base nos valores disponíveis em vez de fazer chamadas de DB (potencialmente caras) toda vez.

Aqui está a minha solução com estas considerações:

  • Eu acho que você está interessado em JPA (não Hibernate)
  • Minha solução não requer estender qualquer class e deve funcionar para qualquer bean de entidade JPA, é apenas uma class simples que você usa, nem requer a implementação de qualquer serviço ou DAO . O único requisito é que o conversor dependa diretamente da biblioteca JPA, que pode não ser muito elegante.
  • Utiliza methods auxiliares para serializar / desserializar o id do bean. Ele apenas converte o id do bean de entidade e compõe a string com o nome da class e o id serializado e convertido em base64. Isso é possível devido ao fato de que em jpa os ids das entidades devem implementar serializáveis. A implementação desses methods está no java 1.7, mas você pode encontrar outras implementações para java <1.7
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutput;
 import java.io.ObjectOutputStream;

 import javax.faces.bean.ManagedBean;
 import javax.faces.bean.ManagedProperty;
 import javax.faces.bean.RequestScoped;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.convert.Converter;
 import javax.faces.convert.ConverterException;
 import javax.persistence.EntityManagerFactory;

 / **
  * Conversor genérico de entidades jpa para jsf
  * 
  * Converte as instâncias de jpa em strings com este formulário: @ Converte de strings para instâncias pesquisando por id em
  * base de dados
  * 
  * É possível graças ao fato de que o jpa requer todos os ids de entidade para
  * implementar serializável
  * 
  * Requer: - Você deve fornecer uma instância com o nome "entityManagerFactory" para ser
  * injetado - Lembre-se de implementar equals e hashCode em toda a sua entidade
  * aulas !!
  * 
  * /
 @ManagedBean
 @RequestScoped
 Classe pública EntityConverter implementa o conversor {

     caractere final estático privado CHARACTER_SEPARATOR = '@';

     @ ManagedProperty (value = "# {entityManagerFactory}")
     private EntityManagerFactory entityManagerFactory;

     public void setEntityManagerFactory (EntityManagerFactory entityManagerFactory) {
         this.entityManagerFactory = entityManagerFactory;
     }

     private static final String vazio = "";

     @Sobrepor
     public Object getAsObject (contexto FacesContext, UIComponent c, valor String) {
         if (valor == null || valor.isEmpty ()) {
             return null;
         }

         int index = value.indexOf (CHARACTER_SEPARATOR);
         String clazz = value.substring (0, índice);
         String idBase64String = value.substring (índice + 1, valor.length ());
 EntityManager entityManager = null;
         experimentar {
             Classe entityClazz = Class.forName (clazz);
             ID do object = convertFromBase64String (idBase64String);

         entityManager = entityManagerFactory.createEntityManager ();
         Objeto object = entityManager.find (entityClazz, id);

             object de retorno;

         } catch (ClassNotFoundException e) {
             throw new ConverterException ("Entidade Jpa não encontrada" + clazz, e);
         } catch (IOException e) {
             throw new ConverterException ("Não foi possível desserializar id da class jpa" + clazz, e);
         }finalmente{
         if (entityManager! = null) {
             entityManager.close ();  
         }
     }

     }

     @Sobrepor
     public String getAsString (contexto FacesContext, UIComponent c, valor do object) {
         if (valor == nulo) {
             retornar vazio;
         }
         String clazz = value.getClass (). GetName ();
         String idBase64String;
         experimentar {
             idBase64String = convertToBase64String (entityManagerFactory.getPersistenceUnitUtil (). getIdentifier (valor));
         } catch (IOException e) {
             throw new ConverterException ("Não foi possível serializar o id para a class" + clazz, e);
         }

         Retornar clazz + CHARACTER_SEPARATOR + idBase64String;
     }

     // MÉTODOS UTILITÁRIOS, (Pode ser refatorado movendo-o para outro lugar)

     public static String convertToBase64String (o object) lança IOException {
         return javax.xml.bind.DatatypeConverter.printBase64Binary (convertToBytes (o));
     }

     object estático público convertFromBase64String (String str) lança IOException, ClassNotFoundException {
         return convertFromBytes (javax.xml.bind.DatatypeConverter.parseBase64Binary (str));
     }

     public static byte [] convertToBytes (object object) lança IOException {
         try (ByteArrayOutputStream bos = novo ByteArrayOutputStream (); ObjectOutput out = novo ObjectOutputStream (bos)) {
             out.writeObject (object);
             return bos.toByteArray ();
         }
     }

     public static Object convertFromBytes (Byte [] bytes) lança IOException, ClassNotFoundException {
         try (ByteArrayInputStream bis = novo ByteArrayInputStream (bytes); ObjectInput em = novo ObjectInputStream (bis)) {
             return in.readObject ();
         }
     }

 }

Use-o como um outro conversor com

  
Intereting Posts