Archivos del sitio usabilidad
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.
Carta al señor Armonia
Pocos de nuestros lectores sabrán que dentro de VIAVANSI tenemos a un blogger ex-semi-profesional… Juan G. Hurtado, aka Armonia.
Juan tenía Armonia, un blog muy técnico muy dedicado a la Web y sobre todo a la capa de cliente (estándares), con una cantidad terrible de visitas. Le hizo bastante conocido en la blogosfera 2.0 hispana, le hacían entrevistas, salía en revistas internacionales… Su blog fue su CV y su reclamo para entrar en VIAVANSI. Ahora es el señor Semántico, nuestro crack de capa de cliente y uno de los secretos de que a nuestros clientes les guste la interfaz de nuestras aplicaciones. Sin embargo, por desgracia, un buen día se hartó y dejó de escribir.
Por eso, como opinan otros muchos internautas, señor armonía: ¡vuelva a escribir! *
*Y si escribes en xnoccio, pues mejor :-)
Yusef en la Cartuja
Ayer estuve en una jornada organizada por Avante Formación en la Cartuja sobre Usabilidad. Un evento de autopromoción, vaya, pero con la presencia de Yusef Hassan, un crack de la usabilidad en España. Lo mejor, casi lo único interesante fue lo que él dijo. Y digo “casi” porque hubo una bollería más que decente en el desayuno, y porque Quino Terceño, de Cero4 nos obsequió con esta perla:
“El programador diseña aplicaciones igual que un minero diseña paisajes: cavando zanjas y haciendo montones”
Citaba a Alan Cooper en su libro About Faces 2.0.
Pardiez, qué lucidez.
Otras cosas que me interesaron:
Yusef: diseño centrado en el usuario; modelo que sustituye al de cascada (secuencial e irreversible) por éste otro, conocido como “modelo lavadora”:

Modelo lavadora del diseño centrado en el usuario
Yusef:
- El cliente no es el usuario
- El cliente no es el diseñador
- Nosotros no somos el usuario
- Desde que los ratones tienen ruedecita, no es necesario desviar el foco para hacer scroll, por lo que el contenido no tiene por qué comprimirse en una pantalla
- Los errores hay que representarlos contextualmente (nos enseñaba el diseño de un procedimiento extenso, con múltiples páginas y formularios)
Quino:
Por último, nos metieron con cuña una presentación de Microsoft Expression, para el diseño de interfaces. Para Ethel, la web del futuro es algo así como “Microsoflash 2.0″. Esta chica se ve que no visita mucho el sitio de O’Reilly.
Supuse que eran ellos quienes pagaban los pastelitos y el café, así que fui prudente y no hice preguntas.
Busca esto rápido
Encuentra lo que buscas de forma sencilla usando el buscador.
Categorías
Encuentra artículos a través de sus "tags"
Archivos mensuales
Encuentra artículos según el mes en el que fueron escritos.