Archivos del sitio tips
Curiosidad: La extraña implementación de NaN
Si probáis a evaluar esta expresión: Double.NaN==Double.NaN , comprobareis con extrañeza que Java retorna false.
Sin embarco, si realizamos la comparación con el método equals, la cosa mejora y todo se comporta como esperábamos.
¿Por qué ocurre esto? ¿Es un Bug?
Por extraño que nos pudiera parecer no es un error en la implementación, y podemos encontrar este comportamiento en otros lenguajes orientados a objetos como C#.
La implementación del tipo double en Java se basa en el estandard “IEEE 754 Binary Floating-Point Arithmetic standard“, que define que dos números con el valor NaN nunca son iguales, por lo que el operador “==” se definió de acuerdo con esta especificación. El problema surge al conciliar este comportamiento con la especificación del método equals para Object, por lo que se decidió sobrescribir el método equals de Double para adaptarlo al comportamiento de comparación de equivalencia esperado. Y aquí tenemos el conflicto “método equals” VS “IEEE 754″.
Por lo que lo recomendado es utilizar el operador cuando deseas hacer comparaciones de acuerdo con el estándar para números flotantes, y utilizar el método equals si en tu código es deseable que un NaN sea igual a un NaN.
La solución.
Si no nos queremos complicar, en la mayoría de los casos bastara con utilizar :
Double.isNaN(varNum) ;
Errores al utilizar Axis2 en Mac OS X Leopard
Al volver a utilizar un proyecto Java EE que invocaba a un servicio web mediante Axis, y que funcionaba correctamente en Windows, me he encontrado con un error bastante extraño que al invocar decía:
java.util.regex.PatternSyntaxException: Dangling meta character ‘*’ near index 0 *.local
Al principio pensaba que podría deberse a que la JVM no estuviese captando la configuración de salida por el proxy de la oficina, pero advertí que el error se daba tanto saliendo a través del proxy, como puenteándolo.
Pues bien, con un poco de Google, me encontré la solución. Resulta que Tomcat dentro de Eclipse recupera la configuración de red de Leopard, y por defecto la variable nonProxyHosts (Omitir ajustes proxy para estos servidores y dominios) tiene el siguiente valor:
*.local, 169.254/16
Que no le gusta nada a Axis2 (se quejaba del *.local)… así que la cambiamos, eliminando esos valores. Para ello, nos vamos a Preferencias del Sistema -> Red -> Avanzado -> Proxies.
Y reiniciando el Tomcat, funcionó correctamente. Viva interné.
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.
Tips: Como crear una instancia de un proxy JAX-WS dinámicamente.
Con JAX-WS 2.x, al igual que era posible con Xfire, podemos crear dinámicamente un proxy de un servicio web sin necesidad de recurrir a tools (wsimport) si disponemos de la interfaz Java del servicio web (SEI) anotada correctamente.
Ej:
@WebService(name=”ServiceWSPort”, targetNamespace=”http://www.xnoccio.com/serviceWS”)
public interface ServiceWS {
@WebMethod
public String ping(Strin hola);
}
Para poder invocar este Servicio Web, lo normal sería generar las clase cliente proxy desde el WSDL, pero un mecanismo mas sencillo es simplemente hacer uso de las capacidades dinámicas de JAX-WS para generar en caliente una implementación cliente de esta interfaz:
Ej:
URL wsdlURL=new URL(”http://hostname:8080/path/ServiceWS?wsdl”);
Qname serviceQname=new Qname(”http://xnoccio.com/serviceWS”,”ServiceWSService);
Qname postQname=new Qname(”http://xnoccio.com/serviceWS”,”ServiceWSPort);
Service service=Service.create(wsdlURL,serviceQname);
ServiceWS clienteProxyWS=(ServiceWS) service.getPort(portQname,ServiceWS.class);
clienteProxyWS.ping(”hola mundo”);
Preprocesado de peticiones SOAP en JAX-WS
En el caso de que necesitemos establecer algún tipo de preprocesamiento/filtro/mecanismo de seguridad a un servicio web, una de las formas más interesantes es utilizando los manejadores HandlerChain que proporciona JAX-WS 2.x.
En primer lugar implementamos el manejador, que nos permitirá realizar procesamientos y postprocesamientos sobre las peticiones SOAP que lleguen a nuestro servicios web. Os dejo un ejemplo de un manejador que permite filtrar por ip:
public class SecurityServiceWebHandler implements MessageHandler{
private static Log log=LogFactory.getLog(SecurityServiceWebHandler.class);
public Set getHeaders() {
return null;
}
public void close(MessageContext context) {
}
public boolean handleFault(MessageHandlerContext context) {
return true;
} /** Comprueba que las ips que acceden a la aplicación son efectivamente ip permitidas.
* @see javax.xml.ws.handler.Handler#handleMessage(javax.xml.ws.handler.MessageContext)
*/
public boolean handleMessage(MessageHandlerContext context) {
ServletRequest servletRequest = ((ServletRequest)context.get(MessageContext.SERVLET_REQUEST));
// Obtenemos la ip remota que invoca al Servicio Web
String remoteAddres=servletRequest.getRemoteAddr();
String allowed=”192.168.10.160,80.58.0.12″;
if(StringUtils.contains(allowed, remoteAddres)){
if(log.isInfoEnabled())log.info(”Servicio Web solicitado desde ip: “+remoteAddres);
}else{
log.error(”Acceso denegado. La ip “+remoteAddres+” no tiene permiso para acceder a los WS.”);
throw new WebServiceException(”Acceso denegado. La ip “+remoteAddres+” no tiene permiso para acceder a los WS.”);
}
return true;
}
}
Una vez implementado el manejador, lo siguiente es publicarlo en el fichero handlerchain.xml
<handler-chains xmlns=”http://java.sun.com/xml/ns/javaee”>
<handler-chain>
<handler>
<handler-class>org.viafirma.conector.security.SecurityServiceWebHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>
El último paso es asociar nuestro servicio web con el manejador, mediante la anotación @HandlerChain.
@HandlerChain(file="handlerchain.xml")
@WebService(serviceName="ConectorFirmaRMIService",targetNamespace = "http://viafirma.org/client/", name = "ConectorFirmaRMIClient",portName="ConectorFirmaRMI",
endpointInterface = "org.viafirma.cliente.firma.rmi.FirmaClienteRMI")
public class ConectorFirmaRMI extends UnicastRemoteObject implements FirmaClienteRMI {
Como ejemplo, gracias a este código podremos recuperar y registrar las ips de todas las peticiones entrantes a nuestro servicio web, y denegar las ips no admitidas.
Consejos para desarrolladores de OpenCms
Hace unas semanas mientras impartía formación de OpenCms estuve hablando con un técnico de otra empresa y me comentó los problemas que estaban teniendo al manejar un proyecto en OpenCms 6. Al cabo de unos minutos nos dimos cuenta de que los problemas que tenía no se debían a OpenCms (más bien, yo me di cuenta y él dijo:¡Por fin alguien que me entiende!), sino al diseño del portal que le habían entregado.
Llegados a este punto le estuve comentando los siguientes consejos que nosotros seguimos en Viavansi:
1.- No usar microsites cuando no sea apropiado:
Un microsite es un tipo de carpeta (”carpeta extendida”) que viene en el módulo TemplateOne.
Básicamente se utiliza para dar una funcionalidad distinta a una parte del portal. Si vamos a crear un portal desde cero no debe estar contenido en un microsite, sino en una carpeta normal. Si vamos a actualizar a futuras versiones de OpenCms ni siquiera debemos usarlo, es más, en la instalación por defecto de OpenCms 7.0.4 no aparece (se recomienda no instalar el módulo TemplateOne y usar el nuevo TemplateTwo).
2.- Si programas para OpenCms 6 o superior es mejor que olvides la forma de trabajar que tenías en OpenCms 5.
3.- Crea una estructura correcta:
Es muy importante crear un portal sólido, con una estructura lógica. Las plantillas deben estar en una carpeta en nuestro módulo y la parte navegable debe estar en /sites/ o /sites/default/ (dependiendo de como configures el fichero opencms-system.xml). Olvidate de cosas del estilo http://misitio.com/com.misitio/resources?noticia=/sites/actualidad/noticia_0005.html.
Si tu estructura es correcta puedes ver la noticia escribiendo http://misitio.com/actualidad/noticia_0005.html.
(Doy fe de haber visto cosas como esa en más de una ocasión).
4.- No añadas las jsp a las fuentes de las búsquedas:
La búsqueda por defecto muestra una descripción de cada elemento en los resultados. Si añadis las jsp y os ponen como búsqueda “include” las descripciones pueden mostrar cosas como cms.loginWebUser(usuario,contraseña), etc.
5.- Cuidado con las distintas versiones de OpenCms:
Hay que conocer cómo se comporta cada versión de OpenCms (qué bugs o qué cosas se implementaron según qué versión).
OpenCms 6.x hasta OpenCms 6.2.2 valida antes de cerrar los campos de los XSD, esto fue corregido en la 6.2.3.
OpenCms 7.0 hasta OpenCms 7.0.3 no mapea de campos a atributos, OpenCms 7.0.4 sí.
Para conocer estos detalles no hay nada mejor que leerse las notas de cada versión.
6.- Corrige los errores conocidos:
Hay errores del desarrollador que son fácilmente reconocibles en OpenCms, por ejemplo si el menú flotante aparece n veces repetido significa que al modificar el fichero opencms-modules.xml has repetido n veces un id de recurso.
7.- Copias de seguridad: Las copias de seguridad no se deben hacer sólo de la base de datos:
Si no tenemos los archivos de configuración (en especial el opencms-modules.xml) es muy dificil que restauremos el sistema.
8.- No tocar el código fuente de OpenCms:
La mejor forma de poder actualizar a futuras versiones es que el código fuente de OpenCms no se haya modificado. Si hacemos un portal y no hemos respetado este punto lo más seguro es que tarde o temprano nos arrepintamos.
9.- OpenCms tiene una línea de aprendizaje fácil (comparada con otras tecnologías, por supuesto), pero es muy difícil llegar a dominarlo. Hay que echarle muchas horas, hacer muchas pruebas, etc.
10.- Comunidad:
No estais sólos con OpenCms. Existe una comunidad muy grande de desarrolladores que os pueden echar una mano. Los principales recursos de OpenCms son:
* Página de OpenCms.
* Lista de correos oficial (en inglés).
* Foro de OpenCms (en inglés).
* OpenCms Hispano (en español).
* Grupo en facebook.(en inglés). Buscar por OpenCms, claro.
* OpenCms wiki (en inglés).
Tips: Problemas con el tiempo de “Leasing” en sistemas de cálculo distribuidos
Desde hace unos meses llevo trabajando en un proyecto que requiere una enorme capacidad de calculo para su correcto funcionamiento, por lo que he tenido que recurrir a un sistema de calculo en cluster basado en JavaSpace/Jini y ComputeFarm. Al principio el sistema funcionaba correctamente, pero a medida que he ido añadiendo cpus (1 mac, 1 portátil Linux, 1 portátil Windows Vista, 2 pcs con XP, 4 pcs antiguos con Linux y una PlayStation3) ha empezado a bloquearse y a dejar el cluster inutilizado. El problema surge al ser un cluster muy heterogéneo, lo que causa que mientras en algunas cpus el tiempo de calculo requerido por tarea sea solo de algunos segundos, en otras máquinas el tiempo de calculo requerido es muy elevado. En estas condiciones surge de forma “aleatoria” la siguiente excepción:
net.jini.core.transaction.UnknownTransactionException: unknown transaction at com.sun.jini.mahalo.TxnManagerImpl.join(TxnManagerImpl.java:759)... at com.sun.jini.mahalo.TxnManagerImpl_Stub.join(Unknown Source)... at com.sun.jini.mahalo.TxnMgrProxy.join(TxnMgrProxy.java:131)... at com.sun.jini.outrigger.SpaceProxy.write(SpaceProxy.java:298)
¿Y por que no reconoce la transacción Jini?
Cuando se produce una situación en la que le “toca” realizar una tarea muy costosa a una cpu lenta, se activa el timeout de la transacción y por lo tanto ya no hay una transacción a la que commitear el resultado del cálculo, y esto hace que las tareas no puedan ser liberadas, causando la caída del cluster. Una vez entendido el problema, la solución es muy sencilla:
- Si la implementación del worker es ComputeFarm 0.8, es suficiente con modificar la variable de sistema mediante: -Dorg.tiling.computefarm.impl.javaspaces.MaxTaskExecutionTime=1500000 ( para por ejemplo permitir tiempos de espera de 25 minutos)
- Si la implementación del worker es ComputeFarm 0.7, como es mi caso debido a dependencias con otras librerías, hay que recompilar el proyecto para configurar manualmente este parámetro, modificando el código fuente de org.tiling.computefarm.WorkerThread.
Una vez solucionado este punto, y aunque mi experiencia en este tipo de soluciones es muy reducida, tengo que admitir que el pack “JavaSpace/Jini/ComputeFarm” es una maravilla para la creación sencilla de sistemas de cálculo distribuidos.
Matar un proceso externo desde Java (por nombre, no por pid)
Recientemente nos hemos enfrentado a un problema en uno de nuestros proyectos.
Resulta que el servidor OpenOffice que usamos en ese proyecto para determinadas tareas, en algunas circunstancias se queda totalmente “tonto”. El proceso esta ahi, el servidor no hace crash, pero no responde a ninguna llamada.
Decidimos cortar por lo sano, y hacernos un daemon que chequease si el servidor estaba respondiendo. Si no responde, matamos el servidor, y lo rearrancamos.
No tiene mucho misterio el tema.
Pero nos enfrentamos a algo que nunca antes habiamos hecho: Como matar un proceso que no habiamos iniciado nosotros y del que unicamente sabiamos su nombre?
Bueno, buscando un poco en google, y sacando ideas de un foro aqui, y otro foro alli, llegamos a desarrollar esta funcion que quizas le sea de ayuda a alguien que tenga en el futuro un problema similar.
Por supuesto el daemon debe tener privilegios de nivel suficiente para ser capaz de acabar con el proceso a matar, en nuestro caso “soffice”.
private void matarSoffice() {
String osName = System.getProperty("os.name");
String cmd = "";
if(osName.toUpperCase().contains("WIN")){//S.O. Windows
cmd+="tskill soffice";
}else{//Solo ha sido probado en win y linux
cmd+="killall soffice";
}
Process hijo;
try {
hijo = Runtime.getRuntime().exec(cmd);
hijo.waitFor();
if ( hijo.exitValue()==0){
System.out.println("soffice matado con exito");
}else{
System.out.println("Incapaz de matar soffice. Exit code: " + hijo.exitValue()+"n");
}
} catch (IOException e) {
System.out.println("Incapaz de matar soffice.");
} catch (InterruptedException e) {
System.out.println("Incapaz de matar soffice.");
}
}
Hala pues,
Extraño problema con Boolean en Java 5
Un desconcertante comportamiento de Java con el autoboxing de los tipos boolean,Si tenemos las siguientes linea, funciona correctamente.
Boolean repetible=null;if(!( repetible==null))
Sin embargo, si sustituimos == por != nos encontramos con un misterioso error que provoca un “java.lang.NullPointerException at booleanValue()“.
Boolean repetible=null;if(repetible!=null)
Analizando un poco el tratamiento que le da java al autoboxing, podemos ver que el comportamiento tiene su sentido, aunque no deja de ser un tanto curioso.En el primer caso, la comparación funciona correctamente ya que el operador == aplicado a Objetos comprueba si los dos objetos son iguales, sin hacer una llamada al booleanValue() para generar el tipo primitivo.En el segundo caso al ser ambos operadores booleanos, una de las optimizaciones de la Máquina Virtual nos juega una mala pasada. En esta caso, el operador != se comporta diferente, ya que el operador de negación trabaja con el valor primitivo del Boolean, invocando a booleanValue().
Actualización
A raíz del comentario de Pablo Moretti, me he dado cuenta que estaba en un error, no es Java 5 el que tiene el problema, si no el JDT de Eclipse 3.3. Para reproducir el poblema:
public static void main(String[] args) {
Boolean b=null;;
// Este código falla en Eclipse 3.3 al ejecutar en modo debug
// un inspect sobre la condición
if(b!=null){
System.out.println("B no es null");
}
}
Iniciamos la ejecución en modo depuración, seleccionamos con el cursor (b!=null) e inspeccionamos su valor.
Seam en aplicaciones Standalone
Jboss Seam es un magnífico framework para el desarrollo de aplicaciones Web, pero plantea algunos problemas, cuando deseamos reutilizar nuestro código Seam desde una aplicación que no se esta ejecutando dentro de un servidor de aplicaciones o desde un Thread independiente. Esto es debido a que sin la envoltura del servidor de aplicaciones, ninguno de los contextos Seam estarán disponibles.
El error típico con el que nos vamos a encontrar es:
java.lang.IllegalStateException: No application context active
at org.jboss.seam.Component.forName(Component.java:1807)
Código para inicializar Seam antes de utilizarlo:
/**
* Inicializa el contexto Seam.
* Necesario para ejecutar código cuando el contexto Seam no esta inicializado y necesitamos acceder a elementos del contexto.
* Con esto evitamos la excepción "No application context active"
* @author Félix García Borrego
*/
public static void initContextApplication(){
if(!Contexts.isApplicationContextActive()){
// Si el contexto no esta inicializado lo creamos
if(!Lifecycle.isApplicationInitialized()){
// La aplicación no esta activa, la activamos
MockServletContext servletcontextMock = new MockServletContext();
ServletLifecycle.beginApplication(servletcontextMock);
new Initialization(servletcontextMock).create().init();
}
// Inicializamos el contexto
ServletLifecycle.beginInitialization();
}
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.