Arroz con Mango: Seam + Logger & Finder Interceptor + Lucene…

Bueno, tuve unas necesidades en un proyecto, así que comencé por hacer un GenericDAO, luego le agregué un FinderInterceptor el cual permite que cuando hagas findByWhatever te busque un NamedQuery que esté definido en la Entidad de JPA, como tip le agregué un logger el cual loguea cada método conjunto con los parámetros pasados, aqui comienzo a explicar uno por uno:

GenericDAO; interfase que define el contrato del JPA DAO el cual implementa de forma genérica las funcionalidades del mismo; esta interfase está interceptada por un Finder del cual hablaremos más tarde; tuve que usar Lucene ya que el proyecto corre sobre MSSQL Server y el bendito no soporta translate:


package com.viavansi.fdu.persistencia.DAO;
import java.io.Serializable;
import java.util.List;

import org.apache.lucene.queryParser.ParseException;

import com.viavansi.framework.core.persistencia.servicios.excepciones.ExcepcionPersistencia;/**
 * Interface for data access objects.
 *
 * <p>
 * Generic Interface DAO which provides the basic contracted operations for
 * every DAO; an implementation is also provided.
 *
 * @param <T>
 * The persistent class.
 * @param <PK>
 * The class of the primary key of the persistent class.
 */
@FinderExecutor
public interface GenericDao<T, PK extends Serializable> { /**
  * Merge.
  *
  * @param persistentObject
  */
 void update(T persistentObject) throws ExcepcionPersistencia; /**
  * Make the instance persistent.
  *
  * @throws ExcepcionPersistencia
  */
 void create(T newInstance) throws ExcepcionPersistencia; /**
  * Make the object transient.
  *
  * @param persistentObject
  * @throws ExcepcionPersistencia
  */
 void delete(T persistentObject) throws ExcepcionPersistencia; /**
  * Returns a persistent object specified by its key.
  *
  * @throws ExcepcionPersistencia
  */
 T read(PK id) throws ExcepcionPersistencia; /**
  * Returns all persistent entities.
  */
 List<T> findAll() throws ExcepcionPersistencia; /**
  * Resolves and executes a finder. <p/>
  * <p>
  * This implementation uses the short name of the type class, appending a .
  * and the method name so the name of the query to look up becomes
  * Pet1.findByName if the method is findByName and the type Pet1. <p/>
  * <p>
  * An other implementation would be useful as well that does not return a
  * list but a single object.
  */
 List<T> executeFinder(String method, Object[] queryArguments)
  throws ExcepcionPersistencia; List<T> searchByText(String expresion) throws ExcepcionPersistencia,
  ParseException;

Ahora definimos el DAO que contiene injectado el EntityManager:


package com.viavansi.fdu.persistencia.DAO;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.jboss.seam.annotations.In;

import com.viavansi.framework.core.excepciones.CodigoError;
import com.viavansi.framework.core.persistencia.servicios.excepciones.ExcepcionDatosNoEncontrados;
import com.viavansi.framework.core.persistencia.servicios.excepciones.ExcepcionPersistencia;

/**
 * Generic data access object.
 *
 * @param <T>
 * The persistent class.
 * @param <PK>
 * The class of the primary key of the persistent class.
 */
public abstract class GenericJpaDaoFDU<T, PK extends Serializable> implements
  GenericDao<T, PK> {

protected Class<T> type;

@In("fduPersistenceContext")
 protected EntityManager entityManager;

public EntityManager getEntityManager() {
  return entityManager;
 }

public void setEntityManager(EntityManager entityManager) {
  this.entityManager = entityManager;
 }

public void create(T newInstance) throws ExcepcionPersistencia {
  try {
  this.entityManager.persist(newInstance);
  } catch (Exception e) {
  throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
  }
 }

public void delete(T persistentObject) throws ExcepcionPersistencia {
  try {
  this.entityManager.remove(persistentObject);
  } catch (Exception e) {
  throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
  }
 }

public T read(PK id) throws ExcepcionDatosNoEncontrados {
  T t = (T) this.entityManager.find(type, id);
  if (t == null) {
  throw new ExcepcionDatosNoEncontrados(
  CodigoError.ERROR_DATOS_NO_ENCONTRADOS,
  "Datos no encontrados");
  }
  return t;
 }

public void update(T transientObject) throws ExcepcionPersistencia {
  try {
  this.entityManager.merge(transientObject);
  } catch (Exception e) {
  throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
  }
 }

@SuppressWarnings("unchecked")
 public List<T> findAll() throws ExcepcionPersistencia {
  try {
  return entityManager.createQuery(
  "select obj from " + this.type.getName() + " obj")
  .getResultList();
  } catch (Exception e) {
  throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
  }
 }

/**
  * Resolves and executes a finder. <p/>
  * <p>
  * This implementation uses the short name of the type class, appending a .
  * and the method name so the name of the query to look up becomes
  * Pet1.findByName if the method is findByName and the type Pet1. <p/>
  * <p>
  * An other implementation would be useful as well that does not return a
  * list but a single object.
  *
  * @throws ExcepcionPersistencia
  */
 @SuppressWarnings( { "unchecked" })
 public List<T> executeFinder(String method, Object[] queryArguments)
  throws ExcepcionPersistencia {
  try {
  String queryName = queryNameFromMethod(method);
  Query query = entityManager.createNamedQuery(queryName);
  for (int i = 0; i < queryArguments.length; i++) {
  query.setParameter(i, queryArguments[i]);
  }
  return query.getResultList();
  } catch (Exception e) {
  throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
  }
 }

/**
  * Resolves the name of the named query.
  *
  * @param finderMethod
  * "findPerson, etc."
  * @return
  */
 protected String queryNameFromMethod(String finderMethod) {
  return type.getSimpleName() + "." + finderMethod;
 }

/*
  * (non-Javadoc)
  *
  * @see
  * com.viavansi.fdu.persistencia.DAO.GenericDao#findWhere(java.lang.String)
  */
 @SuppressWarnings("unchecked")
 public List<T> searchByText(String expression)
  throws ExcepcionPersistencia, ParseException {
  PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance()
  .getPropertyUtils();
  StringBuilder builder = new StringBuilder();
  boolean firstField = true;
  for (PropertyDescriptor descriptor : propertyUtils
  .getPropertyDescriptors(type)) {
  if (firstField) {
  firstField = false;
  } else {
  builder.append(" OR ");
  }
  builder.append(descriptor.getName() + ":" + expression);
  }
  FullTextEntityManager fullTextEntityManager = Search
  .createFullTextEntityManager(entityManager);
  QueryParser parser = new QueryParser("id", new ISOLatin1Analyzer());
  org.apache.lucene.search.Query luceneQuery = parser.parse(builder
  .toString());
  Query query = fullTextEntityManager.createFullTextQuery(luceneQuery,
  type);
  List result = query.getResultList();
  if (result.size() == 0) {
  throw new ExcepcionDatosNoEncontrados();
  }
  return result;
 }

/**
  *
  * @param <T>
  * @param expression
  * @param entityManager
  * @param type
  * @return
  * @throws ExcepcionPersistencia
  * @throws ParseException
  */
 @SuppressWarnings("unchecked")
 public static <T> List<T> searchByText(String expression,
  EntityManager entityManager, Class<T> type)
  throws ExcepcionPersistencia, ParseException {
  PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance()
  .getPropertyUtils();
  StringBuilder builder = new StringBuilder();
  boolean firstField = true;
  for (PropertyDescriptor descriptor : propertyUtils
  .getPropertyDescriptors(type)) {
  if (firstField) {
  firstField = false;
  } else {
  builder.append(" OR ");
  }
  builder.append(descriptor.getName() + ":" + expression);
  }
  FullTextEntityManager fullTextEntityManager = Search
  .createFullTextEntityManager(entityManager);
  QueryParser parser = new QueryParser("id", new ISOLatin1Analyzer());
  org.apache.lucene.search.Query luceneQuery = parser.parse(builder
  .toString());
  Query query = fullTextEntityManager.createFullTextQuery(luceneQuery,
  type);
  return query.getResultList();
 }

}

Nuestro 1er DAO para una Entidad; como verán, se apoya en el GenericDAO, y en su implementación:


package com.viavansi.fdu.persistencia.DAO;
import java.util.List;

import org.jboss.seam.annotations.Name;

import com.viavansi.fdu.persistencia.VO.ProcessInfoVO;

/**
 * @author gmedina
 *
 */
@Name("processInfoDAO")
public class ProcessInfoDAO extends GenericJpaDaoFDU<ProcessInfoVO, Long> {

public ProcessInfoDAO() {
  this.type = ProcessInfoVO.class;
 }

public List<ProcessInfoVO> findByProcessName(String name) {
  return null;
 }

public List<ProcessInfoVO> findByJbpmName(String jbpmName) {
  return null;
 }

public List<ProcessInfoVO> findByArea(String area) {
  return null;
 }

}

Vamos a mostrar nuestro Finder interceptor el cual sigue el Interceptor pattern y está soportado por Seam; muy sencillo, si el método se llama findByWhatever el busca un named query llamado findByWhatever, en tu implementación del DAO solo debes retornar null, si retornas algo entonces el finder se anula automáticamente:


package com.viavansi.fdu.interceptor;
import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.intercept.InvocationContext;

import com.viavansi.fdu.persistencia.DAO.GenericDao;

/**
 * @author gmedina
 *
 */
public class FinderInterceptor { /**
  *
  * @param invocation
  * @return
  * @throws Throwable
  */
 @AroundInvoke
 @SuppressWarnings("unchecked")
 public Object executeFinder(InvocationContext invocation) throws Throwable {
  String methodName = invocation.getMethod().getName();
  if (methodName.startsWith("findBy")) {
  GenericDao dao = (GenericDao) invocation.getTarget();
  Object result = invocation.proceed();
  return result == null ? dao.executeFinder(methodName, invocation
  .getParameters()) : result;
  } else
  return invocation.proceed();
 }

Nuestro persistence.xml con la configuración de Lucene que necesita:


<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
 <!--
  Conexión por defecto de la aplicación utilizando POOL de conexiones
 -->
 <persistence-unit name="fduPersistenceUnit"
  transaction-type="RESOURCE_LOCAL">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <jta-data-source>java:comp/env/jdbc/fdu</jta-data-source>
  <class>com.viavansi.fdu.persistencia.VO.UsuarioVO</class>
  <class>com.viavansi.fdu.persistencia.VO.MiembroVO</class>
  <class>com.viavansi.fdu.persistencia.VO.RolVO</class>
  <class>com.viavansi.fdu.persistencia.VO.ProcessInfoVO</class>
  <properties>
  <property name="hibernate.show_sql" value="true" />
  <property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
  <property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider" />
  <!--
  Configuración para el soporte de prefijos en Hibernate. Estrategia
  para generación de nombres de tablas asociadas a anotaciones Table
  EJB3.0.
  -->
  <property name="hibernate.ejb.naming_strategy"
  value="com.viavansi.framework.persistencia.jpa.NamingStrategy" />
  <property name="hibernate.search.default.directory_provider"
  value="org.hibernate.search.store.FSDirectoryProvider" />
  <property name="hibernate.search.default.indexBase"
  value="/Java/lucene/fdu/app" />
  </properties>
 </persistence-unit> </persistence>

Finalmente nuestro DAO el cual tiene anotaciones de JPA y Hibernate Search/Lucene


package com.viavansi.fdu.persistencia.VO;
import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.DateBridge;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Resolution;
import org.hibernate.search.annotations.Store;

import com.viavansi.fdu.persistencia.DAO.ISOLatin1Analyzer;

/**
 * @author gmedina
 *
 */
@NamedQueries( {
  @NamedQuery(name = "ProcessInfoVO.findByProcessName", query = "from ProcessInfoVO processInfo where processInfo.processName = ?"),
  @NamedQuery(name = "ProcessInfoVO.findByJbpmName", query = "from ProcessInfoVO processInfo where processInfo.jbpmName = ?"),
  @NamedQuery(name = "ProcessInfoVO.findByArea", query = "from ProcessInfoVO processInfo where processInfo.area = ?") })
@Indexed
@Analyzer(impl = ISOLatin1Analyzer.class)
@Entity
@Table(name = "`${PREFIX_FDU}PROCESS_INFO`")
public class ProcessInfoVO implements Serializable {

private static final long serialVersionUID = 1570433106072090149L;

private Long id;
 private String processName;
 private String jbpmName;
 private Date startDate;
 private Date dueDate;
 private boolean active;
 private String area;

/**
  * @return the id
  */
 @Id
 @DocumentId
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 public Long getId() {
  return id;
 }

/**
  * @param id
  * the id to set
  */
 public void setId(Long id) {
  this.id = id;
 }

/**
  * @return the processName
  */
 @Field(index = Index.TOKENIZED, store = Store.NO)
 @Column(name = "PROCESS_NAME", unique = true, nullable = false, insertable = true, updatable = true, length = 255)
 public String getProcessName() {
  return processName;
 }

/**
  * @param processName
  * the processName to set
  */
 public void setProcessName(String processName) {
  this.processName = processName;
 }

/**
  * @return the jbpmName
  */
 @Field(index = Index.TOKENIZED, store = Store.NO)
 @Column(name = "JBPM_NAME", unique = true, nullable = false, insertable = true, updatable = true, length = 255)
 public String getJbpmName() {
  return jbpmName;
 }

/**
  * @param jbpmName
  * the jbpmName to set
  */
 public void setJbpmName(String jbpmName) {
  this.jbpmName = jbpmName;
 }

/**
  * @return the startDate
  */
 @Field(index = Index.UN_TOKENIZED)
 @DateBridge(resolution = Resolution.DAY)
 @Temporal(TemporalType.DATE)
 @Column(name = "START_DATE", unique = false, nullable = true, insertable = true, updatable = true)
 public Date getStartDate() {
  return startDate;
 }

/**
  * @param startDate
  * the startDate to set
  */
 public void setStartDate(Date startDate) {
  this.startDate = startDate;
 }

/**
  * @return the dueDate
  */
 @Field(index = Index.UN_TOKENIZED)
 @DateBridge(resolution = Resolution.DAY)
 @Temporal(TemporalType.DATE)
 @Column(name = "DUE_DATE", unique = false, nullable = true, insertable = true, updatable = true)
 public Date getDueDate() {
  return dueDate;
 }

/**
  * @param dueDate
  * the dueDate to set
  */
 public void setDueDate(Date dueDate) {
  this.dueDate = dueDate;
 }

/**
  * @return the active
  */
 @Column(name = "ACTIVE", unique = false, nullable = false, insertable = true, updatable = true)
 public boolean isActive() {
  return active;
 }

/**
  * @param active
  * the active to set
  */
 public void setActive(boolean active) {
  this.active = active;
 }

/**
  * @return the area
  */
 @Field(index = Index.TOKENIZED, store = Store.NO)
 @Column(name = "AREA", unique = false, nullable = false, insertable = true, updatable = true, length = 100)
 public String getArea() {
  return area;
 }

/**
  * @param area
  * the area to set
  */
 public void setArea(String area) {
  this.area = area;
 }

/*
  * (non-Javadoc)
  *
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  return result;
 }

/*
  * (non-Javadoc)
  *
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
  return true;
  }
  if (obj == null || !(obj instanceof ProcessInfoVO)) {
  return false;
  }
  ProcessInfoVO other = (ProcessInfoVO) obj;
  if (id == null) {
  if (other.id != null) {
  return false;
  }
  } else if (!id.equals(other.id)) {
  return false;
  }
  return true;
 }

}

Nota: Lucene necesita construir un indice inicial, les pegaré un pedazo de código de como hacer esto:


@SuppressWarnings("unchecked")
 public void reIndexLuceneDB() {
  FullTextEntityManager fullTextEntityManager = Search
  .createFullTextEntityManager(entityManager);
  Class[] entityClasses = { UsuarioVO.class, RolVO.class,
  ProcessInfoVO.class };
  for (Class entityClass : entityClasses) {
  for (Object object : entityManager.createQuery(
  "select obj from " + entityClass.getName() + " obj")
  .getResultList()) {
  fullTextEntityManager.index(object);
  }
  }
 }

Bueno, he pegado tanto código que explicarlo todo en un solo post es difícil, asi que manden sus preguntas.

Que lo disfruten.

Guido.

Comentarios

Comments are closed.