JEE6, haciéndolo fácil.

3
13042

JEE6, haciéndolo fácil.

Los fuentes

Lo primero es el enlace a los fuentes de este tutorial

Introducción

Ya estamos en el mes de julio y en Madrid hace un calor espantoso. A pesar de las inclemencias del tiempo y de la dificultad
que supone poner en marcha las neuronas en este momento, es una ocasión ideal para ponerse a cacharrear con
los nuevos juguetes que aparecen en escena en el mundo Java.
La nueva versión Enterprise de Java ya lleva unos cuantos meses con nosotros y es el momentos de empezar
a tantear un poco que nos trae o que se lleva. La versión anterior ya fué todo un cambio en su planteamiento
buscando sobre todo la sencillez en el desarrollo.

Ya hablé algo en tutoriales anteriores acerca de
la versión de EJB 3.0
que me sorprendió gratamente en su momento, y tengo que decir, que si bien la mayor parte de las nuevas incorporaciones
de esta versión y de la anterior no son ninguna novedad (ya que la mayor parte de ellas son incorporaciones de ideas de otros frameworks),
me satisface comprobar que los creadores de esta nueva versión han sido lo bastante inteligentes y humildes para incorporar las mejores
cosillas que ofrecen los demás frameworks que hoy día chapotean en nuestro mundillo.
Tras cacharrear, leer algún libro y mirar mucho por la red, se puede resumir que:

Cosillas nuevas en JEE6

  • Búsqueda de un framework más sencillo. Casi todo se puede hacer con anotaciones y desaparece la necesidad de usar descriptores.
  • Búsqueda de un framework más portable. Se incluyen especificaciones para normalizar los nombres JNDI de los EJBs (lo que era siempre un problema entre servidores) y se incluye el concepto de EmbeddedContainer para poder probar unitariamente los EJBs.
  • Búsqueda de un framework más ligero. Debido a la gran cantidad de especificaciones que se han de cumplir,
    la nueva versión ha incorporado algunos conceptos nuevos para tratar de minimizar este impacto:

    1. Pruning: Esto sería algo parecido al deprecated. Han decidido incluir el concepto de Especificaciones que serán eliminadas (EJB 2.X, JAX-RPC, JAXR …)
    2. Profile (perfiles): Los servidores JEE incorporarán perfiles diferentes en función de la naturaleza de las aplicaciones que corran en nuestro servidor.
      Este concepto aparecía en el servidor JBoss desde sus inicios (minimal, all, default …). Por ahora, la nueva versión únicamente ha definido el Web Profile (Perfil Web) que incluye JSF, JSP, JSTL, Servlet, EL, EJB Lite, JPA, JTA y Commons Annotations
    3. EJB Lite: es un subconjunto de las especificaciones más importantes de EJB para poder ser incluído por ejemplo en el Web Profile.
  • Búsqueda de un framework más completo. Se incluyen nuevas especificaciones como por ejemplo RESTFul Webservices (JAX-RS).

Sin más, y como el movimiento se demuestra andando, probemos algunas cosas nuevas. Lo primero que necesitamos es alguien que implemente todo esto. Tenemos varias elecciones, glassfish 3, JBoss 6 …
Esta vez toca glassfish:

Descargando Glassfish v3

Como siempre hay versiones de todos los colores. Yo he escogido la versión OpenSource sin instalador (el zip)
El enlace está aquí.

Lo único que hecho es descomprimirlo.

Configurando el entorno.

Para el tutorial, he aprovechado para instalar el Eclipse Helios (última versión de eclipse), pensando que incluiría
el glassfish v3 en la pestaña de Servers. Pero no lo incluye. Así que finalmente, lo he instalado buscando en el market place:


Lo configuramos en la pestaña de servers:


Cuidado porque nos pedirá usar la JDK, no vale con la JRE…



Arrancamos y comprobamos que va bien la cosa…





Vamos a comenzar con el modelo y el DAO:

JPA 2.0

La nueva versión de JPA no supone un impacto con respecto a la anterior. Básicamente las incorporaciones más importantes:

  • API de generación de Queries dinámicas mediante POO. Yo prefiero usar JPQL, pero…es cuestión de gustos
  • Tatatachán….delete orphans is here….¿ al estilo hibernate ? pues no. No es igual. Aquí es algo más parecido a un borrado en cascada.
    En hibernate significaba que si sacas un elemento de la colección, se borraba automáticamente.
    No entiendo entonces su aportación, ya que esto se podía hacer con operaciones en cascada en el otro lado de la relación.
  • Se incluye el bloqueo pesimista (el select … for update)
  • Se aumenta la sintaxis de JPQL
  • API de caché de segundo nivel
  • La anotación @OrderColumn para mantener el orden de colecciones

Vamos a crearnos un proyecto maven (webapp) que nos va a servir para los ejemplos usando el plugin IAM de eclipse:







Debéis cambiar el web.xml para que utilice los namespaces de la nueva versión:


  Aplicación Web JEE6 de Citas de Paco

Ahora es momento de seleccionar una implementación de JPA…Os dejo un enlace muy interesante con comparaciones entre
las implementaciones más comunes. Yo iba a elegir la implementación de Hibernate antes de ver esto, pero ahora con más razón:
Ver comparaciones JPA

Configuremos el pom.xml:


	4.0.0
	citas
	citas
	war
	1.0-SNAPSHOT
	citas Maven Webapp
	http://maven.apache.org
	
		3.5.1-Final
		4.0.2.GA
	
	
		
			repository.jboss.org
			JBoss Repository
			http://repository.jboss.org/maven2
		
		
		
			java.net
			Glassfish
			http://download.java.net/maven/glassfish
		
		
			maven2-repository.dev.java.net
			Java.net Repository for Maven 2
			http://download.java.net/maven/2 
			default
		
	
	
		
		
			junit
			junit
			4.8.1
			test
		
		
		
			org.hibernate
			hibernate-entitymanager
			${hibernate-version}
		
		
		
			org.hibernate
			hibernate-validator
			${hibernate-validator-version}
		
		
		
			org.slf4j
			jcl-over-slf4j
			1.5.10
			runtime
		
		
			org.slf4j
			slf4j-api
			1.5.10
			runtime
		
		
			org.slf4j
			slf4j-log4j12
			1.5.10
			runtime
		

		
		
			org.glassfish.extras
			glassfish-embedded-all
			3.0.1
			test
		
		
		
			org.glassfish
			javax.ejb
			3.1-b11
			provided
		
		
		
			org.glassfish
			javax.persistence
			3.0-b29
			provided
		
		
		
		
			commons-lang
			commons-lang
			2.5
		
		
	

	
		
			
				org.apache.maven.plugins
				maven-compiler-plugin
				2.0.2
				
					1.6
					1.6
				
			
			
			
				maven-clean-plugin
				2.4.1
									
					
						
							${basedir}
							
							gfembed*/**
																
							false							
						
					
				
			
		
	

Ahora configuraremos el fichero persistence.xml en: src/main/resources/META-INF


	
		org.hibernate.ejb.HibernatePersistence		
		jdbc/citasDatabase		
		com.autentia.citas.model.Cita
		com.autentia.citas.model.Contacto
					
			
			
			
		
	


Nos creamos nuestras clases del modelo. Como en sus sistema de citas:

package com.autentia.citas.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Contacto implements Serializable{

	@Id
	@GeneratedValue
	private Long id;
	
	private String nombre;
	
	private String apellidos;	
	
	@ElementCollection(fetch=FetchType.LAZY)	
	private List telefonos = new ArrayList(0);
	
	@ElementCollection(fetch=FetchType.LAZY)
	private List emails =new ArrayList(0);
	
	@OneToMany(mappedBy="contacto",orphanRemoval=true,cascade=CascadeType.PERSIST,fetch=FetchType.EAGER)	
	private List citas =new ArrayList(0);

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public String getApellidos() {
		return apellidos;
	}

	public void setApellidos(String apellidos) {
		this.apellidos = apellidos;
	}

	public List getTelefonos() {
		return telefonos;
	}
	
	public List getEmails() {
		return emails;
	}	
	
	public List getCitas() {
		return citas;
	}	

	public Long getId() {
		return id;
	}
	
	public void addCita(Cita cita) {
		citas.add(cita);
		cita.setContacto(this);
	}
	
	public void addTelefono(String telefono) {
		if(!telefonos.contains(telefono)) {
			telefonos.add(telefono);
		}
	}
	
	public void addEmail(String email) {
		if(!emails.contains(email)) {
			emails.add(email);		
		}
	}
	
	public void removeCita(Cita cita) {
		if(citas.contains(cita)) {
			citas.remove(cita);
			cita.setContacto(null);
		}
	}
	
	public void removeTelefono(String telefono) {
		if(telefonos.contains(telefono)) {
			telefonos.remove(telefono);			
		}
	}
	
	public void removeEmails(String email) {
		if(emails.contains(email)) {
			emails.remove(email);			
		}
	}
	
	@Override
	public int hashCode() {
		return new HashCodeBuilder().append(nombre).append(apellidos).toHashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		final Contacto other = (Contacto)obj;
		return new EqualsBuilder().append(nombre, other.nombre).append(apellidos, other.apellidos).isEquals();
	}
	
}

package com.autentia.citas.model;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Cita implements Serializable {
	@Id
	@GeneratedValue
	private Long id;
	
	private String asunto;	
	
	private Date fecha;
	
	@ManyToOne
	private Contacto contacto;

	public String getAsunto() {
		return asunto;
	}

	public void setAsunto(String asunto) {
		this.asunto = asunto;
	}

	public Date getFecha() {
		return fecha;
	}

	public void setFecha(Date fecha) {
		this.fecha = fecha;
	}

	public Contacto getContacto() {
		return contacto;
	}

	void setContacto(Contacto contacto) {
		this.contacto = contacto;
	}

	public Long getId() {
		return id;
	}	
}

Una vez creado nuestro modelo, empezaremos con el DAO, pero para eso nos apoyaremos en EJB 3.1:

EJB 3.1

La nueva versión de EJB da un salto más hacia la simplicidad, es decir, mucho más con mucho menos.
Entre las cosas más interesantes:

  • Se marcan para eliminar: Entity Bean 2.X, EJB QL, JAX-RPC.
  • No son obligatorios los interfaces locales y remotos en Session Beans
  • Podemos desplegar EJBs en un war.
  • API Estandar para contenedores embebidos. Lo que simplificará bastante nuestras pruebas unitarias de EJBs
  • Por fin…el Singleton EJB
  • Se amplía bastante el Timer Service, sobre todo la capacidad de scheduling…
  • Llamadas asíncronas a métodos de EJBs por fin. Antes teníamos que montar un circo a través de colas, MDBs etc… para una simple llamada no bloqueante.
  • Estandarización de los nombres JNDI de los EJBs… Esto definitivamente está muy bien.
  • AOP…Interceptores y cosillas así.

Probaremos algunas de estas cosas a lo largo del tutorial.
Vamos ahora a crearnos un simple Dao para nuestro proyecto de citas con EJBs.
Primero definimos el interfaz del Dao:

package com.autentia.citas.dao;

import java.io.Serializable;
import java.util.List;

public interface  Dao {
	public List getAll(Class entityClass);
	public T findById(Class entityClass, Serializable id);
	public List findByQuery(String query);
	public void create(T entity);
	public void delete(T entity);
	public T update(T entity);
}

A continuación, lo implementamos con JPA en un EJB:

package com.autentia.citas.dao;

import java.io.Serializable;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class JPADaoImpl implements Dao{

	@PersistenceContext(unitName="citas")
	private EntityManager manager;

	public List getAll(Class entityClass) {
		return manager.createQuery("select e from "+entityClass.getName()+" as e",entityClass).getResultList();
	}

	public T findById(Class entityClass, Serializable id) {		
		return manager.find(entityClass, id);
	}

	public List findByQuery(String query) {
		return manager.createQuery(query).getResultList();
	}

	public void create(T entity) {
		manager.persist(entity);		
	}

	public void delete(T entity) {
		manager.remove(manager.merge(entity));		
	}

	public T update(T entity) {
		return manager.merge(entity);		
	}
	
}

Fijáos que no hemos marcado en ningún momento, ni el interfaz ni en la clase @Remote ni @Local
ni nada similar. Tampoco hemos creado ningún proyecto diferente de tipo ejb o jar. Es decir, el EJB
va a estar en el proyecto web.

Ahora, todo código que se precie debería se acompañado con una o varias pruebas unitarias que certifiquen
que la cosa funciona. Creo el Test y una clase de Utilidades para levantar el contenedor:

package com.autentia.citas.dao;

import java.util.Calendar;
import javax.naming.NamingException;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.autentia.citas.model.Cita;
import com.autentia.citas.model.Contacto;
import com.autentia.citas.utils.Utils;

public class DaoTest {	
	
	@BeforeClass
	public static void initTests() throws Exception {		
		if(Utils.container==null) {
			Utils.startContainer();
		}
	}
	
	@Test
	public void createCita() throws NamingException {
		
		Contacto contacto = new Contacto();
			contacto.setNombre("Francisco Javier");
			contacto.setApellidos("Martínez Páez");
			contacto.addEmail("fjmpaez@autentia.com");
			contacto.addEmail("fjmpaez@acme.com");
			contacto.addTelefono("915551111");
			contacto.addTelefono("620999999");		
		
		Cita cita = new Cita();		
			cita.setAsunto("Quedada en el parque con los niños");
			Calendar cal = Calendar.getInstance();
			cal.set(Calendar.YEAR, 2014);
			cal.set(Calendar.DAY_OF_MONTH, 21);
			cal.set(Calendar.MONTH, 7);
			cita.setFecha(cal.getTime());		
			
			cal.set(Calendar.YEAR, 2012);
			cal.set(Calendar.DAY_OF_MONTH, 21);
			cal.set(Calendar.MONTH, 7);		
		
		Cita cita2 = new Cita();		
			cita2.setAsunto("Reunión de seguimiento proyecto ACME LA CAMA");
			cita2.setFecha(cal.getTime());
		
		// Usamos el contexto JNDI para buscar el EJB...Y usando el nombre estandarizado...
		Dao dao =(Dao)Utils.ctx.lookup("java:global/citas/JPADaoImpl");
		
		contacto.addCita(cita);
		contacto.addCita(cita2);		
		
		// Persistimos contacto y en cascada se persistirán las citas 
		dao.create(contacto);	
		
		
		Assert.assertTrue(dao.getAll(Cita.class).size()==2);		
		Assert.assertTrue(dao.getAll(Contacto.class).size()==1);
		Assert.assertTrue(dao.findByQuery("select cita from Cita cita inner join cita.contacto contacto where contacto.nombre like 'Francisco Javier'").size()==2);		
	}
	
	
	@Test
	public void deletingAll() throws NamingException {
		Dao dao =(Dao)Utils.ctx.lookup("java:global/citas/JPADaoImpl");
		Contacto contacto = (Contacto) dao.getAll(Contacto.class).get(0);
		
		// El orphans delete entra en juego eliminado las citas
		dao.delete(contacto);
		
		Assert.assertTrue(dao.getAll(Cita.class).size()==0);		
		Assert.assertTrue(dao.getAll(Contacto.class).size()==0);
	}
	
}

package com.autentia.citas.utils;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;

public class Utils {
	
	public static EJBContainer container;
	public static Context ctx;	
	
	public static void startContainer() throws Exception {
		
		// Inicializamos el EJB Container...		
		Map properties = new HashMap();
		// Le decimos donde están los EJBs
		properties.put(EJBContainer.MODULES, new File("target/classes"));
		// Le damos un nombre a la aplicación
		properties.put(EJBContainer.APP_NAME, "citas");		
		container = EJBContainer.createEJBContainer(properties);		
		ctx=container.getContext();		
	}
	
	
	public static void closeContainer() {		
		container.close();
	}
}

Esperad… no le déis todavía al test que todavía hemos de configurar unas cosillas.
Para que el embedded container funcione correctamente necesitamos un fichero de configuración
llamado domain.xml, y para evitar una excepción, el fichero server.policy en las rutas de la imágen:



Esos ficheros los podéis obtener de la instalación del glassfish que os habéis descargado o en el
fichero: glassfish-embedded-all-3.0.1.jar (lo tendréis en el repositorio local de maven en [RUTA_REPO]\.m2\org\glassfish\extras\)
Eso es lo que hice yo… Vosotros tenéis los del ejemplo.
Ahora debemos configurar en el fichero domain.xml el datasource de nuestras citas. Os pongo únicamente lo que hay que incluir:

  ...
  
    
    
    
    
    
     
    
    
      
      
    
    
      
      
      
      
      
      
     
    
          
    
      
      
         
    
      
  
  ...

Ahora sí…dale al botón de probar el DaoTest (con JUnit) …o bien desde la consola. Veréis como se levanta
el contenedor embebido, se despliegan los ejbs, el datasource y se cierra al final del los tests:



Y lo mejor es que funciona.

Vamos a continuar haciendo un poco de negocio.
Vamos a crearnos un Stateless y le llamaremos CitasManager.
Éste nos servirá para probar algunas cosas nuevas:

package com.autentia.citas.managers;

import java.util.Calendar;
import java.util.List;
import java.util.concurrent.Future;
import javax.annotation.Resource;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.EJB;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import com.autentia.citas.dao.Dao;
import com.autentia.citas.model.Cita;
import com.autentia.citas.model.Contacto;

@Stateless
public class CitasManager {
	
	@EJB
	private Dao dao;
	
	@Resource
	private SessionContext ctx;
	
	@SuppressWarnings("unchecked")
	public List getAllCitas() {
		return dao.getAll(Cita.class);
	}
	
	@SuppressWarnings("unchecked")
	public Cita getCita(Long id) {
		return (Cita) dao.findById(Cita.class, id);
	}
	
	@SuppressWarnings("unchecked")
	public Contacto getContacto(Long id) {
		return (Contacto) dao.findById(Contacto.class, id);
	}
	
	public void citarme(Contacto contacto, Cita cita) {
		if(contacto.getId()!=null) {
			dao.create(cita);
			contacto.addCita(cita);
			dao.update(contacto);
		} else {
			contacto.addCita(cita);
			dao.create(contacto);
		}
		
		crearRecordatorio(cita);		
	}
	
	/**
	 * Método que crea un Timer de recordatorio de la cita una hora antes de la misma
	 * @param cita
	 */
	private void crearRecordatorio(Cita cita) {
		// No uso DI para buscar el TimerService por problemas en el contenedor embebido.
		TimerService timerService = null;
		try {
			timerService = (TimerService) ctx.getTimerService();
		} catch (Exception e) {
			
		}
		
		if(timerService!=null) {
			Calendar cal = Calendar.getInstance();
				cal.setTime(cita.getFecha());
				cal.add(Calendar.HOUR, -1);
				timerService.createSingleActionTimer(cal.getTime(), new TimerConfig(cita, true));
		}
	}
	
	/**
	 * Método de callback del timer.
	 * @param timer
	 */
	@Timeout
	private void sendRecordatorio(Timer timer) {
		Cita c = (Cita) timer.getInfo();
		
		// Ahora enviamos el correo al contacto de la cita y a mi...pero esto no lo voy a hacer
	}
	

	@SuppressWarnings("unchecked")
	public List getAllContactos() {
		return dao.getAll(Contacto.class);
	}
	 
	public List getCitasWith(Contacto contacto) {
		return dao.findByQuery("select cita from Cita cita inner join cita.contacto contacto where contacto.id = ?1"
				,contacto.getId());
	}
	
	/**
	 * Método asíncrono sin respuesta
	 * @param c
	 */
	@Asynchronous
	public void recuerdaleLaCita(Cita c) {
		// No lo hagáis en casa... ni en producción, es para probar esto...
		try {
			Thread.currentThread().sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// Aquí le mandamos un mail...
		// Lo siento pero paso de hacer esto.
		
	}
	
	/**
	 * Método asíncrono con respuesta
	 * @param c
	 */
	@Asynchronous
	public Future recuerdaleLaCitaAck(Cita c) {
		// No lo hagáis en casa... ni en producción, es para probar esto...
		try {
			Thread.currentThread().sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		// Aquí le mandamos un mail...
		// Y le decimos que OK	
		// Lo siento pero paso de hacer esto.
		return new AsyncResult(true);
	}
	
	/**
	 * Este método nos contará lo que tardan las cosas.
	 * @param inv
	 * @return
	 */
	@AroundInvoke
	private Object cronometro(InvocationContext inv) {
		long time1 = System.currentTimeMillis();
			try {
				return inv.proceed();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			} finally {
				long total = System.currentTimeMillis() - time1;
				System.out.println("La invocación a:"+inv.getTarget()+"->"+inv.getMethod()+" ha tardado: "+total+" ms");
			}		
	}
	
	
}

Ahora haremos un test para probarlo…

package com.autentia.citas.managers;

import java.util.Calendar;
import java.util.List;
import java.util.concurrent.Future;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.autentia.citas.dao.Dao;
import com.autentia.citas.model.Cita;
import com.autentia.citas.model.Contacto;
import com.autentia.citas.utils.Utils;

public class CitasManagerTest {
	
	
	@BeforeClass
	public static void initTests() throws Exception {		
		if(Utils.container==null) {
			Utils.startContainer();
		}
	}
	
	@SuppressWarnings("unchecked")
	@AfterClass
	public static void releaseTests() throws Exception {	
		// Limpiamos la Base de datos para no interferir en otros tests
		Dao dao =(Dao)Utils.ctx.lookup("java:global/citas/JPADaoImpl");
		List contactos = dao.getAll(Contacto.class); 
		for(Contacto c:contactos) {
			dao.delete(c);
		}

	
	}
	
	@Test
	public void createCitas() throws Exception {		// 
		// Usamos el contexto JNDI para buscar el EJB...Y usando el nombre estandarizado...
		CitasManager manager =(CitasManager)Utils.ctx.lookup("java:global/citas/CitasManager");
		
		Cita cita = new Cita();		
			cita.setAsunto("Quedada en el parque con los niños");
			Calendar cal = Calendar.getInstance();
			cal.set(Calendar.YEAR, 2014);
			cal.set(Calendar.DAY_OF_MONTH, 21);
			cal.set(Calendar.MONTH, 7);
			cita.setFecha(cal.getTime());		
			
		Contacto contacto = new Contacto();
			contacto.setNombre("Francisco Javier");
			contacto.setApellidos("Martínez Páez");
			contacto.addEmail("fjmpaez@autentia.com");
			contacto.addEmail("fjmpaez@acme.com");
			contacto.addTelefono("915551111");
			contacto.addTelefono("620999999");
			
		manager.citarme(contacto, cita);
		
		Assert.assertTrue(manager.getAllCitas().size()==1);
		Assert.assertTrue(manager.getAllContactos().size()==1);
		Assert.assertTrue(manager.getCitasWith(contacto).size()==1);
		
		
		// Probamos cita con contacto ya existente
		cal.set(Calendar.YEAR, 2012);
		cal.set(Calendar.DAY_OF_MONTH, 21);
		cal.set(Calendar.MONTH, 7);		
	
		Cita cita2 = new Cita();		
			cita2.setAsunto("Reunión de seguimiento proyecto ACME LA CAMA");
			cita2.setFecha(cal.getTime());
		
		manager.citarme(contacto, cita2);
		
		Assert.assertTrue(manager.getAllCitas().size()==2);
		Assert.assertTrue(manager.getAllContactos().size()==1);
		Assert.assertTrue(manager.getCitasWith(contacto).size()==2);
	}
	
	@Test(timeout=9000)
	public void recuerda1() throws Exception {
		CitasManager manager =(CitasManager)Utils.ctx.lookup("java:global/citas/CitasManager");
		manager.recuerdaleLaCita(null);
	}
	
	@Test
	public void recuerda2() throws Exception {
		CitasManager manager =(CitasManager)Utils.ctx.lookup("java:global/citas/CitasManager");
		// A mi esto del futuro me da miedo. LO ha debido diseñar Doc el de Regreso Al futuro
		long time1 = System.currentTimeMillis();
		Future futuro = manager.recuerdaleLaCitaAck(null);
		long time2 = System.currentTimeMillis();
		Assert.assertTrue((time2-time1)< 9000 );
		
		// Ahora se bloquea esperando respuesta.		
		Boolean result = futuro.get();
		
		long time3 = System.currentTimeMillis();
		Assert.assertTrue((time3-time1)>= 10000 );
		Assert.assertTrue(result);		
		// Hay también una versión con espera activa: futuro.get(timeout, unit)		
	}	
	
}

Ejecutadlo y veréis como todo esto funciona. AOP con el @AroundInvoke (mirad las trazas), los métodos asíncronos,
la inyección de dependencias con @EJB, etc. También he incluido un ejemplo de crear un recordatorio de citas usando Timers.

Ahora lo ejecuto desde la consola de maven:

Bueno, ahora tenemos que empezar con la vista. En esta primera parte únicamente desplegaremos la aplicación
en el glassfish y comprobaremos que se despliega bien.
Antes de eso, debemos configurar también el datasource en el fichero domain.xml de vuestra instalación
tal y cómo lo hemos hecho en el de los tests. La ruta (por si no lo encontráis es [RUTA_GLASSFISH]\domains\domain1\config)

Una vez hecho esto, basta con agregar nuestra aplicación en la pestaña servers.
Seleccionad el servidor glassfish, pulsad el botón derecho y ejecutad Add and Remove…:



Ahora arrancar el servidor pulsando en start:



Vamos a comprobar que se ha desplegado bien. Desde la consola usaremos las herramientas de administración de glassfish: [RUTA_GLASSFISH]\bin]
Ejecutamos:



Y comprobamos que nuestra aplicación es: ejb y web.
Podemos entrar ahora en http://localhost:8080/citas/ y veréis la página que nos creó el arquetipo de maven: Hello World

Vamos con la vista

Para la vista usaremos evidentemente JSF 2.0 + Facelets. Entre las cosas nuevas de JSF 2:

  • Facelets ya forma parte de JSF. No sólo plantillas sino además componentes por composición.
  • Ãmbitos nuevos para los controladores (View y Component).
  • Hola a nuevas anotaciones, adios a los descriptores (o hasta luego, es decir, no se eliminan).
  • Soporte nativo para Ajax.
  • Se incluye un nuevo mecanismo para recuperar recursos (imágenes, css, js …) desde el classpath (En wuija hacíamos lo mismo, aunque supongo que se habrán inspirado en RichFaces)

Os recomiendo el tutorial de Alex antes de empezar, porque yo no me voy a entretener tanto a explicar las cosas: JSF 2

Vamos primero a incluir en el pom.xml la dependencia a JSF 2 y alguna más:


		
			javax.faces
			jsf-api
			2.0
			provided		
		
		
		
		
			javax.servlet
			servlet-api
			2.5
			provided
		
		

Ahora configuraremos JSF en el web.xml


	Aplicación Web JEE6 de Citas de Paco
	
	
	
		
			Define the value returned by Application.getProjectStage(). Allowed values: Production, Development, 
			UnitTest, SystemTest, Extension. Default value is Production.
		
		javax.faces.PROJECT_STAGE
		Development
	
	
	
	
		Faces Servlet
		javax.faces.webapp.FacesServlet
		1
	

	
	
		Faces Servlet
		*.faces
	
	
	
	
		com.autentia.citas.utils.PopulatorListener	
		


En el código del listener podréis comprobar que rellena datos si no hay. Recordad que cuando despleguéis la aplicación
debemos tener configurado en el servidor el datasource que estamos usando, tal y como lo hicimos para el contenedor embebido.

Ahora necesitamos el faces-config.xml donde configuraremos el fichero de mensajes i18n. Este fichero está en resources



   
   
   	
   		es   		
   	
   	
   		messages
   		msg
   	   
      

Ahora nos vamos a crear un view helper en formato ManagedBean (que es el apropiado en estos charcos). Le llamamos CitasController y le decimos que tiene ámbito de session:

package com.autentia.citas.view;

import java.util.List;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import com.autentia.citas.managers.CitasManager;
import com.autentia.citas.model.Cita;
import com.autentia.citas.model.Contacto;

@ManagedBean
@SessionScoped
public class CitasController {
	
	private Cita cita;
	
	private Contacto contacto;	
	
	@EJB
	private CitasManager mgr;
	
	public List getAllCitas() {
		return mgr.getAllCitas();
	}
	
	
	public List getAllContactos() {		
		return mgr.getAllContactos();
	}
	
	
	public Cita getCita() {
		return cita;
	}

	public Contacto getContacto() {
		return contacto;
	}

	public void setContacto(Contacto contacto) {
		this.contacto = contacto;
	}


	public void setCita(Cita cita) {
		this.cita = cita;
	}


	public String newCita() {
		cita = new Cita();		
		return "newCita.xhtml";
	}
	
	public String saveCita() {
		mgr.citarme(contacto, cita);
		return "home.xhtml";
	}
	
}

Ahora nos creamos nuestras primeras páginas:

  • /WEB-INF/templates/defaultLayout.xhtml. Plantilla maestra que dará estructura a nuestras páginas. Creamos una forma sencilla con cabecera, cuerpo y pie.
  • /home.xhtml . Será nuestra página de inicio y usará la plantilla anterior.
  • Cambiamos index.jsp para apuntar a la home.

Os muestro nuestra home:





	
	
		
		    
				
										
						#{msg['cita.asunto']}
					
					
				
				
					
						#{msg['cita.fecha']}
					
					
						
										
				
				
					
						#{msg['cita.contacto']}
					
											
				
			
			
			
		
		


Si desplegamos la aplicación y navegamos a ella, mostrará: (si, es que desde que España ha ganado el mundial,
por estos lares nos sentimos especialmente españoles):



Vamos a continuar creando la página de entrada de una cita:

También hemos usado el converter «selectItemsConverter». Este converter lo he cogido del tutorial de JSF de Alex que os indicaba arriba.

Si pulsamos sobre «Crear una nueva Cita»:





	
	
		
		     		    
		    	
		    	
		    	 
		    	
		    			 
		    	
		    	
		    	
		    		 		    			    		
		    		    	
		    
		    
		    		    			
		
		

 



Si guardamos:


Conclusiones

En cuanto a lo visto a JEE6 me gusta lo que va apareciendo. Todavía sigo pensando que prefiero
usar Hibernate + Spring para el Dao y el negocio que me aportan lo que realmente se necesita para hacer la mayor parte de
las aplicaciones Web y me basta un tomcat para desplegarlo, ya que se agiliza bastante el desarrollo.

Aunque la cosa es que ya me empieza resultar tentador…

He encontrado algunos problemas a la hora de usar el contenedor embebido y el plugin de glassfish para eclipse. En cuanto al primero he encontrado
el problema de la falta de documentación. En cuanto a lo segundo, supongo que se irá afinando en futuras versiones del mismo.

3 COMENTARIOS

  1. Joer que mezcla de gustazo, pique sano y agradecimiento siento al leeros, de verdaz que si el mundo entendiera de informática, que se quitaran los Piques, Casillas y demás… chavales 😉 Vaya equipazo tenéis por Autentia…

  2. Hola Javier gracias por este tutorial esta muy bueno, queria pedirte colaboracion es que estoy tratando de pintar un combo de paises y pues como tu sabes sin necesidad de que haya una accion el debe consultar inicialmente la lista, entonces en el constructor de me Managedbean llamo a un metodo del EJB que me trae la lista pero me sale que esta nulo la instancia remota del ejb, pero cuando lo llamo desde una accion de la vista funciona perfectamente… agradezco si me podes colaborar

  3. Hola Javier,
    Me he bajado del código y al ejecutar el test, produce el siguiente error:

    java.lang.IllegalStateException: Unable to retrieve EntityManagerFactory for unitName citas.

    Creo que el problema es que no encuentra el fichero persistence.xml.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad