07.25.08
Posted in general at 12:55 am por tes
Vamos a ampliar la información del post anterior con conceptos más avanzados.
Partimos de la base de que tenemos configurado y funcionando el módulo UrlRewrite.
El primer problema con el que me topé es que mi aplicación usaba identificadores numéricos, tipo .../product.seam?idProduct=123. Por lo tanto necesitaba traducir el identificador numerico a una palabra descriptiva del producto, como .../product/silla o .../product/mesa.
Afortunadamente con Seam eso no presenta mucha dificultad y puede emplearse una EL Expression tipo #{rewriter.fromNameToId('silla')}.
Por desgracia, en el archivo urlrewrite.xml no pueden incluirse EL Expressions.
Para solucionarlo, creé una 'página virtual' en el pages.xml que recogía los parámetros como String de la url y mediante una llamada a un componente de Seam los traducía en el correspondiente identificador numérico, aprovechando que en el archivo pages.xml sí que se pueden usar EL Expressions.
- Fichero urlrewrite.xml
XML:
<urlrewrite>
<rule>
<from>^/pruduct/([A-Za-z]*)$</from>
<to last="true">/virtual_product.seam?name=$1</to>
</rule>
</urlrewrite>
- Fichero pages.xml
XML:
.....
<page view-id="/virtual_product.xhtml" action="#{product.init}">
<param name="name"/>
<navigation>
<redirect view-id="/product.seam">
<param name="idProduct" value="#{urlRewriter.idProductFromName(name)}"/>
</redirect>
</navigation>
</page>
.....
- Componente rewrite
JAVA:
public int idProductFromName
(String name
) {
// Consulta en la bbdd para halar
// el id del producto a partir del nombre
}
Permalink
07.16.08
Posted in seam at 10:36 pm por tes
Hoy en día están muy de moda las aplicaciones web y blogs con URLs amigables, tipo http://seamcity.madeinxpain.com/archives/category/seam.
Estas URLs además de ser más intuitivas y agradables a la vista, también son más agradables para buscadores de internet como google (o podríamos decir más agradables para EL BUSCADOR de internet).Esta es una de las técnicas seo más simples, destinadas a mejorar tu posicionamiento en buscadores.
En Seam podemos obtener estas URLs sencillas a través del filtro URLrewrite. Este filtro básicamente se encarga de traducir URLs, y es muy muy fácil de usar.
Una de las principales ventajas es que es una capa independiente que se coloca por encima de la aplicación. No es necesario modificar ni una línea de código ya existente, simplemente definir cómo se van a traducir las direcciones.
- Añadir el archivo urlrewrite.jar que viene incluido en la distribución de Seam en la carpeta WEB-INF/lib del proyecto. La mejor forma de hacerlo es mediante el archivo de ant que crea el seam-gen
- Configurar el filtro UrlRewrite en el web.xml tal y como se indica aqui
- Crear el fichero urlrewrite.xml que contiene las reglas de traduccion de urls en la carpeta WEB-INF. Aqui pongo el fichero del ejemplo seambay, pero lo mejor es mirar la documentacion oficial
XML:
<!DOCTYPE urlrewrite
PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
"http://tuckey.org/res/dtds/urlrewrite3.0.dtd">
<urlrewrite>
<rule>
<from>^/feedback/([A-Za-z0-9]*)$</from>
<to last="true">/feedback.seam?member=$1</to>
</rule>
<rule>
<from>^/itemdetail/([0-9]*)$</from>
<to last="true">/auction.seam?id=$1</to>
</rule>
<rule>
<from>^/bidhistory/([0-9]*)$</from>
<to last="true">/bidhistory.seam?id=$1</to>
</rule>
</urlrewrite>
En otro post esplicaré con más detalle las posibilidades del fichero urlrewrite.xml, cómo usar parámetros, cómo traducir de nombres a identificadores numéricos, y cómo conseguir que las urls amigables las pueda generar nuestra aplicación tras el envío de formularios o acciones.
Permalink
07.04.08
Posted in javascript at 12:34 am por tes
En un post anterior hablaba de como añadir un tooltip personalizado a nuestras marcas de google maps, que permitia mostrar cualquier objeto DOM como si fuese un tooltip de la marca.
Siguiendo la misma idea, podemos hacer un menu contextual para las marcas, que se active al hacer click sobre ellas, y contenga acciones a ejecutar sobre la marca en cuestion.
Para ello seguiremos los siguientes pasos:
- Crear el menu y sus acciones
- Asignar el menu a la marca, y una referencia de la marca al menu para poder ejecutar acciones sobre ella
- Mostrar el menu con el evento onclick y ocultarlo al seleccionar una opción
ver ejemplo
Permalink
06.30.08
Posted in javascript at 10:12 pm por tes
Al situar el ratón encima de una imagen o cualquier elemento del DOM, éste es capaz de mostrar en un tooltip el texto de su atributo 'title',
ejemplo
En las marcas de Google Maps este efecto puede conseguirse especificando el atributo 'title' en el constructor de la marca.
JavaScript:
var coords = new GLatLng(41.647471, -0.885569);
var mark = new GMarker(coords, {title:'texto de ejemplo'})
ver ejemplo
El problema es que esta opción es muy limitada, ya que se restringe al uso de texto plano. Lo ideal sería poder mostrar cualquier elemento del DOM que queramos, con su HTML y su CSS.
Para conseguirlo vamos a seguir estos pasos:
- Crear el nodo del DOM que queramos mostrar o coger un nodo ya existente y ponerlo como un atributo de la marca
- Al escuchar el evento mouseover de la marca, añadir el nodo al div contenedor del mapa en el la posición en la que se encuentra la marca
- Al escuchar el evento mouseout de la marca, eliminar el nodo del div contenedor del mapa para ocultarlo.
ver ejemplo
Permalink
06.20.08
Posted in javascript at 12:43 am por tes
En ocasiones nos interesa restringir el desplazamiento del usuario sobre el mapa a un área determinada, o limitar los niveles de zoom máximo y mínimo.
En el caso del zoom la solución es bastante sencilla. Solo hay que sobreescribir los métodos getMinimumResolution() y getMaximumResolution() de cada uno de los tipos de mapas GMapType presentes en nuestro mapa.
Para el caso del desplazamiento, la solución es algo más compleja. Consiste en escuchar el evento move, que es lanzado por el objeto GMap cuando hay un desplazamiento, y comprobar las coordenadas del mapa tras el desplazamiento. Si estas coordenadas quedan fuera del área a la que queremos limitar el desplazamiento, usamos el metodo GMap.setCenter para que el mapa quede dentro de nuesta área.
Esta clase implementa la funcionalidad deseada:
JavaScript:
/****** TRestricter **********************************************/
// Constructor
TRestricter = function (map) {
this.map = map;
}
// Función que activa la limitación del desplazamiento entre la esquina inferior izquierda
// y la esquina superior derecha
TRestricter.prototype.restrict = function (sw, ne) {
this.map._allowedBounds = new GLatLngBounds(sw, ne);
GEvent.addListener(this.map, 'move', this.checkBounds);
}
// Función que desactiva la limitación del desplazamiento
TRestricter.prototype.unrestrict = function () {
this.map._allowedBounds = null;
}
// Listener encargado de comprobar el desplazamiento
TRestricter.prototype.checkBounds = function() {
if (!this._allowedBounds || this._allowedBounds.contains(this.getCenter())) return;
var x = Math.min(Math.max(this.getCenter().lng(), this._allowedBounds.getSouthWest().lng()), this._allowedBounds.getNorthEast().lng());
var y = Math.min(Math.max(this.getCenter().lat(), this._allowedBounds.getSouthWest().lat()), this._allowedBounds.getNorthEast().lat());
this.setCenter(new GLatLng(y,x));
}
// Establece los límites de zoom del mapa
TRestricter.prototype.zoomLevels = function (min, max) {
var array = this.map.getMapTypes() || [];
for (var i=0; i
array[i].getMinimumResolution = function () { return min };
array[i].getMaximumResolution = function () { return max };
}
}
Y aquí tenemos un ejemplo de la clase en acción
Permalink
06.17.08
Posted in javascript at 1:50 am por tes
En javascript se llama array asociativo a aquellos que en vez de estar organizados con indices numericos en funcion de su posicion dentro del array, están organizados por claves no numericas.
Este tipo de arrays puede ser muy útil si tenemos listas de objetos que poseen un identificador.
JavaScript:
<br />
var array_asociativo = new Array();<br />
array_asociativo['uno'] = 'brinkindans';<br />
array_asociativo['dos'] = 'crusaito';<br />
array_asociativo['tres'] = 'maiquel yason';<br />
array_asociativo['cuatro'] = 'robocó';</p>
<p>alert(array_asociativo('tres')); // --> maiquel yason<br />
Como se puede obervar este tipo de arrays se acerca mas al concepto de Map que al de Array.
Esto ocurre porque los arrays en javascript heredan de la clase Object.
El codigo anterior seria equivalente a este:
JavaScript:
<br />
var array_asociativo = new Object();<br />
array_asociativo.uno = 'brinkindans';<br />
array_asociativo.dos = 'crusaito';<br />
array_asociativo.tres = 'maiquel yason';<br />
array_asociativo.cuatro = 'robocó';</p>
<p>alert(array_asociativo('tres')); //--> maiquel yason<br />
En ambos casos podemos acceder a sus propiedades utilizando la notación de paréntesis o la notacion de puntos.
JavaScript:
<br />
alert(array_asociativo('uno')); //--> brinkindans<br />
alert(array_asociativo.dos); //--> crusaito<br />
El principal problema con estos arrays viene cuando queremos iterar por ellos. Al no estar organizados por indices, no nos vale el típico "for( i=0; i
Deberemos usar un "for in"
JavaScript:
<br />
for(var i in array_asociativo) {<br />
alert(i); // Nombre de la clave<br />
alert(array_asociativo(i)); // valor<br />
}<br />
Sin embargo al iterar de esta forma no solo se enumeran las propiedades del objeto en cuestión, si no también sus funciones por lo que deberíamos hacer una comprobación de tipos mediante el operando "typeof". Este operando devuelve un String con el tipo del operador sobre el que se ejecuta.
Aqui están los resultado al ejecutar el operador "typeof" sobre cada uno de los tipos de javascript.
JavaScript:
<br />
typeof new Object() //--> 'object'<br />
typeof new Array() //--> 'object' , ya que los arrays heredan de los objetos<br />
typeof alert //--> 'function', ya que alert() es una función<br />
typeof 'cadena' //--> 'string', ya que 'cadena' es un String<br />
typeof 21 //--> 'number', ya que es un numero<br />
typeof true //--> 'boolean'<br />
typeof null //--> 'object', null también es considerado como un objeto<br />
typeof undefined //--> 'undefined'<br />
Permalink
06.10.08
Posted in javascript at 11:16 pm por tes
En javascript existen cinco tipos primitivos de datos: undefined, null, boolean, number y string.
A primera vista no se aprecia la diferencia entre los tipos undefined y null.
El tipo undefined corresponde a las variables que han sido definidas y todavía no se les ha asignado un valor.
El tipo null se suele utilizar para representar objetos que en ese momento no existen.
Sin embargo aunque son conceptos distintos el operando de igualdad los considera iguales. No así el operando typeof o el operador de igualdad estricta ===.
JavaScript:
<br />
(undefined == null) //--> true<br />
(undefined === null) //--> false<br />
(typeof undefined == typeof undefined null) //--> false<br />
Si intentamos operar con una variable que tiene valor null, según el contexto devolverá los siguientes resultados.
JavaScript:
Boolean: false<br />
Numeric: 0<br />
String: “null”
Si intentamos operar con una variable que tiene valor undefined, según el contexto devolverá los siguientes resultados.
JavaScript:
Boolean: false<br />
Numeric: NaN<br />
String: “undefined”
Permalink
06.09.08
Posted in general at 1:51 am por tes
Cuando trabajamos con web services en java, a la hora de depurar nos sería muy útil el poder ver el xml (protocolo SOAP) que se envían cliente y servidor.
Sin embargo no existe una forma 'sencilla' de hacerlo.
La idea consiste en hacer una clase que implemente el interface javax.xml.rpc.handler.Handler, capaz de interceptar los mensajes SOAP antes de su envío del cliente al servidor y antes de la recepción por parte del cliente de los mensajes enviados por el servidor.
Pueden añadirse varios handlers en serie para procesar las llamadas (handler chains). Este comportamiento es idéntico al de un ServletFilter en una cadena de filtros.
Desde esta clase tenemos acceso al contenido del mensaje SOAP, por lo que somos capaces de leerlo y escribirlo en un fichero de texto o similar.
En el caso de un cliente hecho con apache axis, podemos obtener la lista de Handlers asociados a un nombre de puerto (javax.xml.namespace.QName) y a un punto de destino a través de la clase org.apache.axis.client.Service. Una vez obtenida la lista, solo nos resta añadir nuestro Handler.
Vamos a ello. En primer lugar implementamos el interfaz javax.xml.rpc.handler.Handler para interceptar la llamada soap:
JAVA:
package com.
madeinxpain.
seamcity.
ws;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.rpc.handler.Handler;
import javax.xml.rpc.handler.HandlerInfo;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
public class SoapLoggingHandler implements Handler{
private HandlerInfo handlerInfo;
private List streams;
public SoapLoggingHandler() {
streams = new ArrayList();
try {
this.addOutputStreamLog(System.out);
this.addFileLog("soap.xml");
} catch (Exception e) {
e.printStackTrace();
}
}
public void addOutputStreamLog(OutputStream os) {
this.streams.add(os);
}
public void addFileLog(String rute) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream(new File(rute));
this.addOutputStreamLog(fos);
}
public boolean handleRequest(MessageContext arg0) {
SOAPMessageContext messageContext = (SOAPMessageContext) arg0;
try {
for (OutputStream os : streams) {
os.write(new String("\n\n<!-- REQUEST -->\n").getBytes());
messageContext.getMessage().writeTo(os);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
public boolean handleResponse(MessageContext arg0) {
SOAPMessageContext messageContext = (SOAPMessageContext) arg0;
try {
for (OutputStream os : streams) {
os.write(new String("\n\n<!-- RESPONSE -->\n").getBytes());
messageContext.getMessage().writeTo(os);
}
} catch (Exception e) {
}
return true;
}
public boolean handleFault(MessageContext arg0) {
SOAPMessageContext messageContext = (SOAPMessageContext) arg0;
try {
for (OutputStream os : streams) {
os.write(new String("\n\n<!-- FAULT -->\n").getBytes());
messageContext.getMessage().writeTo(os);
}
} catch (Exception e) {
}
return true;
}
public void init(HandlerInfo arg0) {
this.handlerInfo = arg0;
}
public void destroy() {
}
public QName[] getHeaders() {
return handlerInfo.getHeaders();
}
}
Esta clase implementa una serie de métodos extra aparte de los obligatorios definidos por el interface. Estos métodos permiten definir una lista de Streams por los que escribir el mensaje.
Estos métodos de logging deben ser invocado desde el constructor, ya que al añadir el Handler a la cadena (como veremos más adelante) no se añade una instancia de un objeto, si no una clase, y es el framework axis el encargado de crear la instancia mediante el método Class.new Instance(), por lo que no es posible definir las formas de logging si no es en el constructor por defecto del handler.
Un a vez codificada la clase, vamos a proceder a añadirla a la cadena. Cada handler debe estar asociado
Tomaremos como ejemplo un cliente WebService para Microsoft Exchange 2007, creado en un post anterior
En primer lugar buscamos la clase que extiende de org.apache.axis.client.Stub, en nuestro caso llamada ExchangeServiceBindingStub, y le añadimos dos campos para almacenar el port por el que se va a realizar la petición soap y el punto de destino.
Estos campos se van a utilizar en el metodo createCall() a la hora de crear la petición soap.
Por simplicidad crearemos los campos como públicos.
JAVA:
public class ExchangeServiceBindingStub
extends org.
apache.
axis.
client.
Stub implements com.
microsoft.
schemas.
exchange.
services._2006.
messages.
ExchangeServicePortType {
public javax.xml.namespace.QName portName;
public String endPoint;
// resto del código original de la clase
// metodo a modificar
protected org.apache.axis.client.Call createCall() throws java.rmi.RemoteException {
// codigo anterior
if (super.cachedPortName != null) {
_call.setPortName(super.cachedPortName);
}
// Tenemos que añadir estas dos lineas
_call.setPortName(this.portName);
_call.setTargetEndpointAddress(this.endPoint);
// codigo posterior
java.util.Enumeration keys = super.cachedProperties.keys();
}
Cuando instanciamos el objeto org.apache.axis.client.Stub le añadimos el handler:
JAVA:
public class ExchangeWebServiceClient
{
private ExchangeServiceBindingStub esb;
private ExchangeServicesLocator locator;
public ExchangeWebServiceClient() {
this.locator = new ExchangeServicesLocator();
this.esb = (ExchangeServiceBindingStub) locator.getExchangeServicePort(new URL(locator.getExchangeServicePortAddress()));
// Añadimos el handler
QName portName = new QName("http://www.neodoo.es/", locator.getExchangeServicePortWSDDServiceName());
List list = locator.getHandlerRegistry().getHandlerChain(portName);
HandlerInfo handlerInfo = new HandlerInfo();
handlerInfo.setHandlerClass(com.madeinxpain.seamcity.ws.SoapLoggingHandler.class);
list.add(handlerInfo);
this.esb.portName = portName;
this.esb.endPoint = locator.getExchangeServicePortAddress();
}
}
Y con esto y un bizcocho, ya tenemos una forma de depurar las llamadas soap a un webservice desde un cliente hecho con axis.
La optimización del código queda a cargo del desarrollador final 
Permalink
06.03.08
Posted in seam at 11:21 pm por tes
Con la llegada de EJB 3 y JPA nace la figura del EntityManager para simplificar la persistencia de objetos.
Y gracias a las anotaciones, el EntityManager puede ser inyectado por el contenedor de EJBs.
Vamos a ver diferentes formas de obtener un EntityManager en Seam a través de anotaciones.
-
JAVA:
@PersistenceContext
EntityManager entityManager;
El EntityManager es inyectado directamente por el EJB container. El ciclo de vida del EntityManager inyectado de esta forma terminará cuando la transacción sea completada.
Tras el cierre de la transacción, las entidades son desacopladas del contexto de persistencia, lo que puede dar lugar a una LazyLoadException.
-
JAVA:
@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager entityManager;
El EntityManager es inyectado directamente por el EJB container, y estará disponible aún después de que la transacción haya sido completa.
Las entidades pueden seguir asociadas al contexto de persistencia durante varias interacciones.
-
JAVA:
@In
EntityManager entityManager;
De esta forma, el EntityManager es inyectado por Seam y asociado al contexto de conversación, con las mismmas características que un EntityManager de tipo extendido.
-
JAVA:
@PersistenceUnit
EntityManagerFactory;
public void getNewEmInstance() {
return emf.createEntityManager();
}
Esta anotacion inyecta un EntityManagerFactory, que permite crear objetos EntityManager mediante el método EntityManagerFactory.createEntityManager();
Permalink
05.30.08
Posted in javascript at 3:58 am por tes
Una de las mejores características de javascript es que se pueden añadir funciones a las 'clases' en cualquier momento a traves de la propiedad prototype.
Aprovechando esta capacidad podemos extender el objeto Array para añadirle funcionalidades de búsqueda y eliminación.
array.indexOf
Devuelve el indice del array en el que se encuentra el elemento. Devuelve -1 si no encuentra el elemento
JavaScript:
Array.prototype.indexOf = function(element) {
for (var i = 0; i <this.length; i++) {
if(this[i] == element) return i;
}
return -1;
};
array.exists
Devuelve true si el elemento se encuentra en el array, haciendo uso de la funcion indexOf
JavaScript:
Array.prototype.exists = function(element) {
return this.indoexOf(element) != -1;
};
array.filter
Devuelve un array que contiene solo los elementos para los que la función pasada como argumento devuelve true
JavaScript:
Array.prototype.filter = function(fn) {
var array = [];
for (var i = 0; i <this.length; i++) if(fn(this[i])) array.push(this[i]);
return array;
};
array.removeIndex
Elimina todos los elementos del array que están entre los índices from y to. Si solo se especifica valor para from, se elimina solo el elemento que se encuentra en ese índice.
JavaScript:
Array.prototype.removeIndex = function(from, to) {
if (from <0) return;
var rest = this.slice((to || from) + 1 || this.length);
this.length = from <0 ? this.length + from : from;
return this.push.apply(this, rest);
};
array.remove
Elimina un elemento del array, usando las funciones removeIndex e indexOf
JavaScript:
Array.prototype.remove = function(e) {
return this.removeIndex(this.indexOf(e));
};
array.execute
Ejecuta la función fn pasandole como argumento cada elemento del array
si le pasamos el argument obj, se ejecutara la funcion fn como si perteneciese al objeto obj
JavaScript:
Array.prototype.execute = function(fn, owner) {
for (var i=0; i<;this.length; i++){
if (owner) fn.call(owner, this[i]);
else if (typeof this[i] != 'function') fn(this[i]);
}
}
array.foreach
Ejecuta la función fn en cada elemento del array, pasandole como argumentos los elementos contenidos en el array args
si le pasamos el argument obj, se ejecutara la funcion fn como si perteneciese al objeto obj
JavaScript:
Array.prototype.foreach = function(fn, args) {
for (var i=0; i<this.length; i++) {
fn.apply(this[i], args);
}
}
Permalink
Siguiente página »»