Páginas

viernes, 26 de abril de 2013

Ejemplo JMS Topic con Spring 2.5 y ActiveMQ

Introducción

En el siguiente ejemplo montamos una demostración de cómo configurar un publicador (publisher) y un suscriptor (suscriber) sobre un topic JMS usando Spring 2.5 para configurar todo el sistema.

Como servidor JMS usaremos Apache ActiveMQ.

En una entrada anterior ya hemos comentado cómo instalar ActiveMQ, así pues, vamos directamente con el ejemplo.

Proyecto publicador (publisher)

Este jar implementa un sencillo publicador de mensajes que entrega sus mensajes a sus N suscriptores mediante una cola de tipo "topic" en un servidor JMS.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-topic-productor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION PRODUCTOR VOLCANDO SOBRE UNA TOPIC -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la TOPIC en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-topic" class="org.apache.activemq.command.ActiveMQTopic">
            <constructor-arg index="0" value="cola-topic"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el JmsTemplate:                        -->
        <!--  pubSubDomain==true => Cola tipo TOPIC           -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="activemq.connectionfactory"/>
            <property name="pubSubDomain" value="true"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el generador de peticiones             -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="productorPeticiones" class="es.ine.sgtic.jms.topic.productor.impl.ProductorPeticionesImpl">
            <property name="jmsTemplate" ref="activemq.jmsTemplate"/>
            <property name="destino" ref="activemq.cola-topic"/>
        </bean>
</beans>


AppTestProductorTopic

package es.ine.app;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import es.ine.sgtic.jms.topic.productor.IProductorPeticiones;

public class AppTestProductorTopic {

    public static void main(String args[]){
   
        //Instanciamos la factoría de beans
        final BeanFactory fabricaDeBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Obtenemos el bean productor de peticiones
        final IProductorPeticiones productor = (IProductorPeticiones) fabricaDeBeans.getBean("productorPeticiones");
       
        //Generamos la petición y ala enviamos
        productor.generarPeticion();
    }
}


IProductorPeticiones.java

package es.ine.sgtic.jms.topic.productor;

public interface IProductorPeticiones {
  
    public void generarPeticion();
}

ProductorPeticionesImpl .java

package es.ine.sgtic.jms.topic.productor.impl;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.Topic;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import es.ine.sgtic.jms.topic.productor.IProductorPeticiones;

public class ProductorPeticionesImpl implements IProductorPeticiones {

    private static final String CAMPO_CONTENIDO = "campo-contenido";
    private static final String MENSAJE = "En un lugar de la mancha";
    private JmsTemplate jmsTemplate = null;
    private Topic destino = null;

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void setDestino(Topic destino) {
        this.destino = destino;
    }

    public void generarPeticion() {
        //Generamos y enviamos una petición
        jmsTemplate.send(this.destino,
                         new MessageCreator() {
                            public Message createMessage(Session session) throws JMSException {
                                MapMessage mensaje = session.createMapMessage();
                                mensaje.setString(CAMPO_CONTENIDO, MENSAJE);
                                return mensaje;
                            }
                        });
    }
}


Proyecto suscriptor (suscriber)

Este jar implementa un sencillo suscriptor que apunta a cierta cola JMS de tipo "topic", por lo que recibe un mensaje cada vez que un publicador deja algo en la cola.

Nosotros sólo implementamos el consumidor de los mensajes (id="consumidorPeticiones"), es Spring quien proporciona un listener (id="listenerPeticiones") sobre la cola "topic".

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-topic-consumidor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jms="http://www.springframework.org/schema/jms"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION CONSUMIDOR LEYENDO DE UNA TOPIC    -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la TOPIC en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-topic" class="org.apache.activemq.command.ActiveMQTopic">
            <constructor-arg index="0" value="cola-topic"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Consumidor de las peticiones                     -->      
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="consumidorPeticiones" class="es.ine.sgtic.jms.topic.consumidor.impl.ConsumidorAvisosTopicImpl"/>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Listener de las peticiones                       -->      
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <jms:listener-container connection-factory="activemq.connectionfactory" destination-type="topic">
            <jms:listener id="listenerPeticiones" destination="cola-topic" ref="consumidorPeticiones"/>
        </jms:listener-container>
</beans> 


AppTestConsumidorTopic.java

package es.ine.sgtic.app;

import javax.jms.JMSException;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jms.listener.DefaultMessageListenerContainer;

public class AppTestConsumidorTopic {

    public static void main(String[] args) throws JMSException {

        //Instanciamos la factoría de beans
        final BeanFactory factoriaBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Instanciamos el bean escuchador de peticiones, con esto queda activo y a la escucha
        final DefaultMessageListenerContainer listenerPeticiones = (DefaultMessageListenerContainer) factoriaBeans.getBean("listenerPeticiones");
       
        //TODO: Si necesitásemos instanciar otro consumidor podríamos invocar:
        //listenerPeticiones.initialize();
    }
}


ConsumidorAvisosTopicImpl.java

package es.ine.sgtic.jms.topic.consumidor.impl;

import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

public class ConsumidorAvisosTopicImpl implements MessageListener {

    private static final String CAMPO_CONTENIDO = "campo-contenido";
  
    @Override
    public void onMessage(Message mensaje) {
        try {
            if (mensaje instanceof MapMessage) {
                final MapMessage mapMessage = (MapMessage) mensaje;
                System.out.println(mapMessage.getString(CAMPO_CONTENIDO));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

lunes, 22 de abril de 2013

Ejemplo JMS Queue con Spring 2.5 y ActiveMQ

Introducción

En el siguiente ejemplo montamos una demostración de cómo configurar un productor y un consumidor sobre una queue JMS usando Spring 2.5 para configurar todo el sistema.

Como servidor JMS usaremos Apache ActiveMQ.

Instalación ActiveMQ

1) Para descargar Apache ActiveMQ desde la página "http://activemq.apache.org/download.html". En la fecha de realización de este ejemplo la última versión disponible es la 5.8.0.

2) Se descomprime la distribución de ActiveMQ descargada, en el directorio que deseemos. En Ubunti se descomprime con:

     tar zxvf apache-activemq-X.X.X-bin.tar.gz

3) Arrancamos el servidor, para lo que necesitaremos lanzar el script "activemq" que tenemos en el subdirectorio bin. La forma de lanzarlo será:

     [dir-instalacion]/bin/activemq start

Nota. También podemos parar reiniciar o verificar el estado del servidor con el mismo script variando el parámetro. Están disponibles los parámetros: stop, restart o status.

4) Para verificar que tenemos correctamente iniciado el servidor, podemos acceder a la consola web del mismo en la URL: http://localhost:8161/ con el usuario/contraseña admin/admin.

5) Para acceder al área de administración del servidor deberemos hacer clic en el enlace "Manage ActiveMQ broker"

Proyecto productor

Este jar implementa un sencillo productor de mensajes que apunta a una cola a la que hemos llamado "cola-queue" en el servidor.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-queue-productor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION PRODUCTOR VOLCANDO SOBRE UNA QUEUE -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la QUEUE en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-queue" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg index="0" value="cola-queue"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el JmsTemplate:                        -->
        <!--  pubSubDomain==false => Cola tipo QUEUE          -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="activemq.connectionfactory"/>
            <property name="pubSubDomain" value="false"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el generador de peticiones             -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="productorPeticiones" class="es.ine.sgtic.jms.queue.productor.impl.ProductorPeticionesImpl">
            <property name="jmsTemplate" ref="activemq.jmsTemplate"/>
            <property name="destino" ref="activemq.cola-queue"/>
        </bean>
</beans>


AppTestProductorQueue

package es.ine.sgtic.app;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import es.ine.sgtic.jms.queue.productor.IProductorPeticiones;

public class AppTestProductorQueue {

    public static void main(String[] args) {
       
        //Instanciamos la factoría de beans
        final BeanFactory fabricaDeBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Obtenemos el bean productor de peticiones
        final IProductorPeticiones productor = (IProductorPeticiones) fabricaDeBeans.getBean("productorPeticiones");
       
        //Generamos la petición y ala enviamos
        productor.generarPeticion();
    } 
}


IProductorPeticiones.java

package es.ine.sgtic.jms.queue.productor;

public interface IProductorPeticiones {
   
    public void generarPeticion();
}


ProductorPeticionesImpl .java

package es.ine.sgtic.jms.queue.productor.impl;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import es.ine.sgtic.jms.queue.productor.IProductorPeticiones;

public class ProductorPeticionesImpl implements IProductorPeticiones {

    private static final String CAMPO_CONTENIDO = "campo-contenido";
    private static final String MENSAJE = "En un lugar de la mancha"; 
    private JmsTemplate jmsTemplate = null;
    private Destination destino = null;

    public void setJmsTemplate(JmsTemplate jmsTemplate) { 
        this.jmsTemplate = jmsTemplate; 
    }

    public void setDestino(Destination destino) {
        this.destino = destino; 
    }

    public void generarPeticion() {
        //Generamos y enviamos una petición
        jmsTemplate.send(this.destino,
                         new MessageCreator() { 
                            public Message createMessage(Session session) throws JMSException { 
                                MapMessage mensaje = session.createMapMessage(); 
                                mensaje.setString(CAMPO_CONTENIDO, MENSAJE); 
                                return mensaje; 
                            }
                        });
    }
}


Proyecto consumidor

Este jar implementa un sencillo consumidor de mensajes que apunta a una cola a la que hemos llamado "cola-queue" en el servidor.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-queue-consumidor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION CONSUMIDOR LEYENDO DE UNA QUEUE    -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la QUEUE en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-queue" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg index="0" value="cola-queue"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el JmsTemplate:                        -->
        <!--  pubSubDomain==false => Cola tipo QUEUE          -->
        <!--  receiveTimeout==xxx => Timeout espera ms        -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="activemq.connectionfactory"/>
            <property name="defaultDestination" ref="activemq.cola-queue"/>
            <property name="pubSubDomain" value="false"/>
            <property name="receiveTimeout" value="1000"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el consumidor de peticiones            -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="consumidorPeticiones" class="es.ine.sgtic.jms.queue.consumidor.impl.ConsumidorPeticionesImpl">
            <property name="jmsTemplate" ref="activemq.jmsTemplate"/>
        </bean>
</beans> 


AppTestConsumidorQueue.java

package es.ine.sgtic.app;

import javax.jms.JMSException;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import es.ine.sgtic.jms.queue.consumidor.IConsumidorPeticiones;

public class AppTestConsumidorQueue {

    public static void main(String[] args) throws JMSException { 

        //Instanciamos la factoría de beans
        final BeanFactory factoriaBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Obtenemos el bean consumidor de peticiones
        final IConsumidorPeticiones consumidor = (IConsumidorPeticiones) factoriaBeans.getBean("consumidorPeticiones");
       
        //Obtenemos y procesamos una petición
        final String peticion = consumidor.consumirPeticion();
        System.out.println(peticion);
    }
}


IConsumidorPeticiones.java

package es.ine.sgtic.jms.queue.consumidor;

import javax.jms.JMSException;

public interface IConsumidorPeticiones {
   
    public String consumirPeticion() throws JMSException;
}


ConsumidorPeticionesImpl.java

package es.ine.sgtic.jms.queue.consumidor.impl;

import javax.jms.JMSException;
import javax.jms.MapMessage;

import org.springframework.jms.core.JmsTemplate;

import es.ine.sgtic.jms.queue.consumidor.IConsumidorPeticiones;

public class ConsumidorPeticionesImpl implements IConsumidorPeticiones {
   
    private static final String CAMPO_CONTENIDO = "campo-contenido";
   
    private JmsTemplate jmsTemplate = null;
   
    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }
   
    public String consumirPeticion() throws JMSException {
       
        //Descargamos y procesamos una petición
        final MapMessage mensaje = (MapMessage) jmsTemplate.receive();
        return mensaje.getString(CAMPO_CONTENIDO);
    } 
}

Introducción a JMS

Introducción

JMS (Java Message Service) es la solución tecnológica ofertada por Java para sistemas de comunicación asíncronos.

Arquitectura de JMS

En la arquitectura JMS participan las siguientes entidades:
  • Clientes JMS. Son las aplicaciones que envian y/o reciben mensajes a través de JMS.
  • Mensajes. Son los datos intercambiados.
  • Destinos JMS. Son los almacenes intermedios donde se almacenan los mensajes.
Destinos JMS

Los servicios de mensajería ofrecen dos tipos de destinos:
  • Colas (tipo queue). Una aplicación (productor) genera un mensaje, lo deposita en la cola y cuando la segunda aplicación (consumidor) se conecta al servicio de mensajería, recoge el mensaje. Habitualmente en una cola sólo participan un productor y un consumidor, pero podrían existir varios productores y varios consumidores colaborando (sistemas de alto rendimiento). En este caso lo fundamental es que cada mensaje se procesa una única vez, no importa qué consumidor lo procese. Los mensajes salen de la cola en orden de llegada, el primero que se recibió es el primero que se recoge (FIFO).

  • Publicador/Suscriptores (tipo topic). Una aplicación (publicador) genera un mensaje, lo publica en el servicio JMS y lo reciben todas las aplicaciones (suscriptores) que esten suscritas al servicio JMS en que se publicó el mensaje. Habitualmente en una cola participa un publicador y muchos suscriptores, pero podrían existir varios publicadores.

Persistencia

Dado que los servicios JMS son asíncronos, los mensajes son almacenados en las colas (tipo queue ó tipo topic) hasta ser recibidos por sus destinatarios. Se debe garantizar que ningún mensaje se pierda incluso cuando el servidor JMS se apague súbitamente. Por ello, los servidores JMS suelen ofrecer la posibilidad de persistir los mensajes en algún medio no volatil como una base de datos.

Seguridad

Como en cualquier mecanismo de comunicación, la seguridad es un aspecto fundamental que siempre debe tenerse en cuenta. Son posibles dos acciones:

Autenticación. Los servidores JMS ofrecen mecanismos para autenticar tanto a los depositantes como a receptores de los mensajes, dado que de otro modo podrían entrar en las colas de mensajes de procedencia desconocida. Lo habitual son comunicaciones SSL. Siempre es posible que las aplicaciones que se comunican a través e JMS implementen sus propios sistemas de autenticación, lo que las hace más interdependientes, pero puede ser necesario si el servidor JMS no es confiable.

Cifrado. Los sistemas de persistencia antes mencionados almacenan los mensajes en una BBDD, lo que los expone a consultas indeseables por parte de terceros. Por ello es siempre conveniente que las aplicaciones cifren los datos intercambiados. El mecanismo de cifrado también se puede dejar en manos del servidor JMS o de la BBDD que persista los mensajes, si ámbos sistemas son confiables.

Disponibilidad

Cualquier sistema de comunicaciones síncrono que pase a ser asíncrono, pasará de dos a tres entidades participando en el intercambio de datos. Si se desea garantizar la tolerancia a fallos 24x7 deberá garantizarse la tolerancia a fallos tanto en las aplicaciones que intercambian los datos como en el servidor JMS.

jueves, 4 de abril de 2013

Openssl. Generar certificado autofirmado para firmar applets

Si necesitamos un certificado autofirmado para firmar applets y poder depurarlos en el navegador podemos usar OpenSSL, que es un conjunto de utilidades de criptografía.

Para instalarlas en Ubuntu deberemos ejecutar:

$ sudo apt-get install openssl

Una vez instalada, podemos verificar si todo está correcto tecleando:

$ openssl version

Lo que nos debería generar una salida del tipo "OpenSSL 1.0.1 14 Mar 2012", que obviamente dependerá de la versión instalada.

Para generar un certificado autofirmado que nos sirva para firmar applets, necesitaremos añadir la extension digitalSignature, por lo que deberemos ejecutar:

$ openssl req -new -x509 -days 3650 -extensions v3_req -keyout key.pem -out cert.pem

Esto desencadena el procedimiento de generación del certificado, el proceso nos solicitará:

- Password (dos veces)
- Código de dos letras del país [ES]
- Provincia [Madrid]
- Ciudad [Madrid]
- Organización [Dune]
- Unidad [Java]
- Nombre [egdp1970]
- eMail [egdp1970@gmail.com]

$ openssl pkcs12 -export -in cert.pem -inkey key.pem -out keystore.p12

Esto desencadena el procedimiento de volcado del certificado en un keystore tipo PKCS12, el proceso nos solicitará:

- Password del certificado (la misma que tecleamos antes dos veces)
- Password del keystore pkcs12 que se va a crear (dos veces)

Nota. v3_req es uno de los niveles de requisitos definicos en el fichero /etc/ssl/openssl.cnf para que el certificado autofirmado sirva para firmar applets deberemos asegurarnos de que contiene:

    [ v3_req ]
    keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment

viernes, 22 de marzo de 2013

Validacion de certificados mediante OCSP en Java

Introducción.

En esta entrada vamos a mostrar como realizar una petición a un servidor OCSP para determinar el estado de revocación de un certificado.

En el ejemplo se han usado las librerías Bouncy Castle (JDK 1.5 - JDK 1.7) disponibles en los repositorios MAVEN.

Dependencias.

Las dependencias del proyecto son:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>es.ine.sgtic</groupId>
  <artifactId>cliente-ocsp</artifactId>
  <version>1.0</version>
  <dependencies>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcprov-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcprov-ext-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcpkix-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcmail-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcpg-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
  </dependencies>
</project>


Codigo.

El código fuente del ejemplo es el siguiente:

package es.ine.sgtic;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;

public class TestOCSP {

//    private static final String URL_OCSP_RESPONDER = "http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder"; 

//    private static final String URL_OCSP_RESPONDER = "http://ocspape.cert.fnmt.es/ocspape/OcspResponder";
//    private static final String URL_OCSP_RESPONDER = "http://ocsp.dnie.es";
    private static final String URL_OCSP_RESPONDER = "http://ocsp.dnielectronico.es/";
  
    public static void main (String args[]) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, OCSPException, OperatorCreationException{

        //Cargamos el almacen de certificados que contiene el certificado a verificar
        final InputStream fis = TestOCSP.class.getClassLoader().getResourceAsStream("almacen.p12");
        final KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(fis,"passwordAlmacen".toCharArray());

        //Sacamos el certificado del que necesitamos conocer su estado de revocacion
        final X509Certificate certAnalizable = (X509Certificate)ks.getCertificate("
aliasCert");

        //Sacamos el certificado raíz de la cadena de certificación
        final Certificate[] chain = ks.getCertificateChain("
aliasCert");
        final X509Certificate certRaiz = (X509Certificate)chain[1];

        //Se carga el proveedor necesario para la petición OCSP
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        //Generamos el ID del cetificado que estamos buscando
        final CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build().get(CertificateID.HASH_SHA1), new X509CertificateHolder(certRaiz.getEncoded()), certAnalizable.getSerialNumber());

        //Instanciamos el generador de requestOCSP
        final OCSPReqBuilder ocspPeticionBuilder = new OCSPReqBuilder();
        ocspPeticionBuilder.addRequest(id);
//
//        //create details for nonce extension
//        final BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
//        final Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, new DEROctetString(nonce.toByteArray()));
//        ocspPeticionBuilder.setRequestExtensions(new Extensions(new Extension[] { ext }));

        //Se genera la petición con el certificado a verificar y su número de serie
        final OCSPReq ocspPeticion = ocspPeticionBuilder.build();

        //Se establece la conexión HTTP con el ocsp del DNIe
        final URL url = new URL(URL_OCSP_RESPONDER);
        final HttpURLConnection con = (HttpURLConnection)url.openConnection();

        //Se configuran las propiedades de la petición HTTP
        con.setRequestProperty("Content-Type", "application/ocsp-request");
        con.setRequestProperty("Accept", "application/ocsp-response");
        con.setDoOutput(true);
        final OutputStream out = con.getOutputStream();
        final DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out));

        //Se obtiene la respuesta del servidos OCSP del DNIe
        dataOut.write(ocspPeticion.getEncoded());
        dataOut.flush();
        dataOut.close();

        //Se parsea la respuesta y se obtiene el estado del certificado retornado por el OCSP
        final InputStream in = (InputStream)con.getContent();
        final OCSPResp ocspRespuesta = new OCSPResp(in);
        int estado = ocspRespuesta.getStatus();

        //Analizamos la respuesta
        if (estado==OCSPResp.SUCCESSFUL) {
            System.out.println("OCSP-RESPONDER: PETICION TRATADA CORRECTAMENTE");
            System.out.println(tratarRespuestaOK(ocspRespuesta));
        } else if (estado==OCSPResp.MALFORMED_REQUEST) {
            System.out.println("OCSP-RESPONDER: ERROR. PETICION MAL FORMADA");
        } else if (estado==OCSPResp.INTERNAL_ERROR) {
            System.out.println("OCSP-RESPONDER: ERROR. ERROR INTERNO DEL SERVIDOR");
        } else if (estado==OCSPResp.TRY_LATER) {
            System.out.println("OCSP-RESPONDER: ERROR. REPETIR LA PETICION MAS TARDE");
        } else if (estado==OCSPResp.SIG_REQUIRED) {
            System.out.println("OCSP-RESPONDER: ERROR. PETICION DEBE SER FIRMADA");
        } else if (estado==OCSPResp.UNAUTHORIZED) {
            System.out.println("OCSP-RESPONDER: ERROR. PETICION OCSP NO ESTA AUTORIZADA");
        } else {
            System.out.println("OCSP-RESPONDER: ERROR. TIPO DE RESPUESTA DESCONOCIDO");
        }
    }

    private static String tratarRespuestaOK(final OCSPResp ocspRespuesta) throws OCSPException, IOException {

        //TODO:Afinar este método
      
        final Object responseObject = ocspRespuesta.getResponseObject();
        final BasicOCSPResp basicOCSPResp = (BasicOCSPResp) responseObject;
      
        final SingleResp[] responses = basicOCSPResp.getResponses();
        if (responses.length == 1) {
            final SingleResp resp = responses[0];
            final Object status = resp.getCertStatus();
            if (status == CertificateStatus.GOOD) {
                return "ESTADO DEL CERTIFICADO: VALIDO";
            }
            else if (status instanceof org.bouncycastle.ocsp.RevokedStatus) {
                return "ESTADO DEL CERTIFICADO: REVOCADO";
            }
            else {
                return "ESTADO DEL CERTIFICADO: DESCONOCIDO";
            }
        }
        return "VARIAS RESPUESTAS. AFINAR EL CODIGO";
    }
}


Alternativas.

Una posible alternativa a picar código en Java es usar openssl.

Las peticiones a los cuatro servidores OCSP indicados en el código equivaldrían a:

openssl ocsp -url http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder -resp_text -issuer FNMT.pem -cert aliasCert.pem
openssl ocsp -url http://ocspape.cert.fnmt.es/ocspape/OcspResponder -resp_text -issuer FNMT.pem -cert
aliasCert.pem
openssl ocsp -url http://ocsp.dnie.es -resp_text -issuer FNMT.pem -cert aliasCert.pem
openssl ocsp -url http://ocsp.dnielectronico.es/ -resp_text -issuer FNMT.pem -cert aliasCert.pem

Pendiente.

Sería interesante realizar las pruebas con un certificado válido, uno revocado y uno caducado (expirado) para analizar a fondo la respuesta contenida en el objeto BasicOCSPResp. En las pruebas, con un certificado expirado tenemos la salida:

OCSP-RESPONDER: PETICION TRATADA CORRECTAMENTE
ESTADO DEL CERTIFICADO: DESCONOCIDO


Hay que profundizar también en el mecanismo para firmar la petición, dado que por ejemplo el servidor: http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder pide la petición firmada.

Por último conviene aclarar si cualquier servidor OCSP ofrece información sobre cualquier certificado o solo por los emitidos por ciertas CA, de ahí el exceso de respuestas con: "ESTADO DEL CERTIFICADO: DESCONOCIDO".

Enlaces de interes:

http://tools.ietf.org/html/rfc2560
http://www.bouncycastle.org/latest_releases.html

sábado, 16 de marzo de 2013

Subreports con JasperReports usando JRBeanCollectionDataSource

Introducción

Siempre que usamos Jasperreports para montar informes, pasamos todos los parámetros, incluso las etiquetas, desde el aplicativo Java. Esto ofrece varias ventajas:
  • Si por ejemplo cambia el motor de la BBDD de un aplicativo en producción, únicamente ajustamos la configuración de Hibernate sin regenerar los informes.
  • Si necesitamos el informe en varios idiomas, controlamos el idioma del usuario desde el aplicativo Java (por ejemplo recogiendo las preferencias del navegador con JSF).
Hasta la fecha siempre montábamos informes simples, es decir, con una única banda de detalle. Ahora que hemos necesitado montar algunos informes con varias bandas de detalle hemos necesitado incluir "subreports" y, como en mecanismo de envío de las distintas "JRBeanCollectionDataSource" asociadas a cada banda de detalle no me ha parecido trivial, dejo resumidos los pasos a realizar.

Clase Java para generar el informe

package org.dune.app;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.JRLoader;

import org.dune.pojo.Coche;
import org.dune.pojo.Telefono;

public class TestReport {

    //Datos cabecera
    private static Map<String,Object> datosCabeceraReport;
   
    //Datos detalle
    private static List<Coche> datosDetalleReport;
    private static List<Telefono> datosDetalleSubReport;
   
    public static void main(String args[]) throws JRException{
       
        //Preparamos los datos de cabecera del report, cuando en
        //el report.jrxml añadamos los Parameter "titulo" y "fecha"
        //tendremos que asegurarnos que sean de tipo "String"
        datosCabeceraReport = new HashMap<String,Object>();
        datosCabeceraReport.put("titulo", "Inventario de bienes");
        datosCabeceraReport.put("fecha", "15/03/2013");
       
        //Preparamos los datos de detalle del report
        datosDetalleReport = new ArrayList<Coche>();
        datosDetalleReport.add(new Coche("1234-AAA", "Peugeot 207"));
        datosDetalleReport.add(new Coche("5678-AAA", "Peugeot 307"));
        datosDetalleReport.add(new Coche("9012-AAA", "Peugeot 407"));
       
        //Preparamos los datos de detalle del subreport
        datosDetalleSubReport = new ArrayList<Telefono>();
        datosDetalleSubReport.add(new Telefono("111111111", "Nokia 1000"));
        datosDetalleSubReport.add(new Telefono("222222222", "Nokia 2000"));
        datosDetalleSubReport.add(new Telefono("333333333", "Nokia 3000"));
       
        //Añadimos los datos de detalle del subreport al Map de parametros del
        //padre y montandolos sobre un JRBeanCollectionDataSource. Cuando en
        //el report.jrxml añadamos el Parameter "datosSubRep" tendremos que
        //forzar el tipo a: "net.sf.jasperreports.engine.data.JRBeanCollectionDataSource"
        datosCabeceraReport.put("datosSubRep", new JRBeanCollectionDataSource(datosDetalleSubReport));
       
        //Montamos el objeto JRMapCollectionDataSource con los datos de la bandas de detalle del report
        final JRBeanCollectionDataSource datosReport = new JRBeanCollectionDataSource(datosDetalleReport);
       
        //Generamos el informe compilado
        creaInforme(datosCabeceraReport, datosReport);
    }
   
    private static void creaInforme(final Map<String,Object> datosCabeceraReport, final JRBeanCollectionDataSource datosReport) throws JRException{
       
        //Cargamos el informe previamente compilado
        final String urlReport = TestReport.class.getResource("/report.jasper").getFile();
        final JasperReport informe = (JasperReport) JRLoader.loadObject(urlReport);

        //Añadimos los datos
        final JasperPrint informePrint = JasperFillManager.fillReport(informe, datosCabeceraReport, datosReport);

        //Exportamos a pdf
        JasperExportManager.exportReportToPdfFile(informePrint,"./src/main/resources/report.pdf");
    }
}


Cuestiones fundamentales

Los detalles fundamentales se han introducido como comentarios en el código de la clase anterior, pero merece la pena repasarlos:
  • Los "parámetros estáticos" del report padre, esto es, todos aquellos que no aparecen en la banda de detalle del informe padre, se pasan al JasperFillManager dentro del objeto: final Map<String,Object> datosCabeceraReport.
  • Los datos de detalle del subreport (lista de telefonos) se pasan al JasperFillManager como un parámetro de tipo JRBeanCollectionDataSource dentro del objeto: final Map<String,Object> datosCabeceraReport .
  • Los datos de detalle del report (lista de coches) padre se pasan al JasperFillManager dentro del objeto: final JRBeanCollectionDataSource datosReport.
  • En el report.jrxml hemos definido los parámetros: titulo, fecha y datosSubRep. Los dos primeros, de tipo String, se usarán en la banda título del report padre. El tercer parámetro, de tipo JRBeanCollectionDataSource, se usará en la propiedad "Data Source Expression" del subreport: $P{datosSubRep}.
  • En el report.jrxml hemos definido los "Fields": matricula y modelo, que son las columnas del área de detalle y que son los atributos de la clase Coche.java.
  • En el subreport.jrxml hemos definido los "Fields": numero y tipo, que son las columnas del área de detalle y que son los atributos de la clase Telefono.java.
Clases java con los "POJO" de las bandas de detalle

La clase Coche.java se usará en la banda de detalle del informe padre.

package org.dune.pojo;

public class Coche {

    private String matricula;
    private String modelo;
       
    public Coche(final String matricula, final String modelo) {
        super();
        this.matricula = matricula;
        this.modelo = modelo;
    }

    public String getMatricula() {
        return matricula;
    }

    public void setMatricula(String matricula) {
        this.matricula = matricula;
    }

    public String getModelo() {
        return modelo;
    }

    public void setModelo(String modelo) {
        this.modelo = modelo;
    }
}


La clase Telefono.java se usará en la banda de detalle del subinforme.

package org.dune.pojo;

public class Telefono {

    private String numero;
    private String tipo;

    public Telefono(final String numero, final String tipo) {
        this.numero = numero;
        this.tipo = tipo;
    }

    public String getNumero() {
        return numero;
    }

    public void setNumero(String numero) {
        this.numero = numero;
    }

    public String getTipo() {
        return tipo;
    }

    public void setTipo(String tipo) {
        this.tipo = tipo;
    }
}


report.jrxml

El aspecto general del informe padre en el diseñador es:


El código xml del informe padre es:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="report1" language="groovy" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
    <property name="ireport.zoom" value="1.4641000000000008"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="titulo" class="java.lang.String"/>
    <parameter name="fecha" class="java.lang.String"/>
    <parameter name="datosSubRep" class="net.sf.jasperreports.engine.data.JRBeanCollectionDataSource"/>
    <field name="matricula" class="java.lang.String"/>
    <field name="modelo" class="java.lang.String"/>
    <title>
        <band height="51" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="4" width="547" height="44"/>
            </rectangle>
            <textField>
                <reportElement x="61" y="26" width="488" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{fecha}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="6" y="6" width="55" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TITULO:]]></text>
            </staticText>
            <staticText>
                <reportElement x="6" y="26" width="55" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[FECHA:]]></text>
            </staticText>
            <textField>
                <reportElement x="61" y="6" width="488" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{titulo}]]></textFieldExpression>
            </textField>
        </band>
    </title>
    <pageHeader>
        <band height="73">
            <rectangle radius="5">
                <reportElement positionType="Float" mode="Opaque" x="2" y="50" width="547" height="22" backcolor="#C68B8B"/>
            </rectangle>
            <rectangle radius="5">
                <reportElement mode="Opaque" x="2" y="1" width="547" height="22" forecolor="#010101" backcolor="#C68B8B"/>
            </rectangle>
            <staticText>
                <reportElement x="6" y="2" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TELEFONOS:]]></text>
            </staticText>
            <staticText>
                <reportElement positionType="Float" x="6" y="51" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[COCHES:]]></text>
            </staticText>
            <subreport>
                <reportElement x="0" y="24" width="555" height="24"/>
                <dataSourceExpression><![CDATA[$P{datosSubRep}]]></dataSourceExpression>
                <subreportExpression class="java.lang.String"><![CDATA["/home/eduardo/trabajo/workspaces-helios/wksTestJasper/TestJasper/src/main/resources/subreport.jasper"]]></subreportExpression>
            </subreport>
        </band>
    </pageHeader>
    <columnHeader>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="2" width="547" height="22"/>
            </rectangle>
            <staticText>
                <reportElement x="104" y="3" width="445" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[MODELO]]></text>
            </staticText>
            <staticText>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[MATRICULA]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="27" splitType="Stretch">
            <rectangle radius="5">
                <reportElement stretchType="RelativeToBandHeight" isPrintRepeatedValues="false" mode="Transparent" x="2" y="2" width="547" height="22" isPrintWhenDetailOverflows="true"/>
            </rectangle>
            <textField>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{matricula}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="104" y="3" width="445" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{modelo}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>


subreport.jrxml

El aspecto general del subinforme en el diseñador es:


El código xml del subinforme padre es:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="subreport" language="groovy" pageWidth="555" pageHeight="100" columnWidth="555" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="0">
    <property name="ireport.zoom" value="1.464100000000001"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <field name="numero" class="java.lang.String"/>
    <field name="tipo" class="java.lang.String"/>
    <columnHeader>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="2" width="547" height="22"/>
            </rectangle>
            <staticText>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[NUMERO]]></text>
            </staticText>
            <staticText>
                <reportElement x="106" y="3" width="441" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TIPO]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement stretchType="RelativeToTallestObject" isPrintRepeatedValues="false" mode="Transparent" x="2" y="1" width="547" height="22" isPrintWhenDetailOverflows="true"/>
            </rectangle>
            <textField>
                <reportElement x="106" y="2" width="441" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{tipo}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="6" y="2" width="100" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{numero}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>


Aspecto del informe generado



Dependencias

Las dependencias Maven del proyecto son:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.dune</groupId>
  <artifactId>TestJasper</artifactId>
  <version>1.0</version>
  <dependencies>
      <dependency>
          <groupId>net.sf.jasperreports</groupId>
          <artifactId>jasperreports</artifactId>
          <version>4.0.0</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>1.7.5</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
  </dependencies>
</project>

miércoles, 13 de febrero de 2013

Primeros pasos con JSON y Java

Introducción

JSON (JavaScript Object Notation), es un formato estándar de intercambio de datos basado en texto que permite a las aplicaciones intercambiar información.

JSON está estandarizado por la IETF (Internet Engineering Task Force) en el RFC-4627 (http://tools.ietf.org/html/rfc4627). El Mime Type para datos en éste formato es 'application/json' y la extensión oficial de los ficheros con este formato es '.json'.

Los datos intercambiados son independientes tanto de la plataforma como del lenguaje de programación utilizado.

¿Qué ventajas ofrece JSON frente a XML?

JSON va reemplazando a XML poco a poco como medio preferido a la hora de intercambiar datos entre aplicativos de distintas plataformas. Los motivos por los que se argumenta que JSON es mejor son casi siempre los mismos:
  • JSON es más fácil de leer (xml también).
  • JSON tiene una estructura es similar a la de Objetos y Arrays (xml también).
  • JSON es más ligero (bytes) en las transmisiones (desde luego que no hay etiqueta de cierre, pero sí de apertura).
  • JSON se parsea más rápido (supongo que será verdad, pero entiendo que dependerá de la calidad del parseador).

Sintaxis JSON

En este apartado vamos a repasar las principales características de la sintaxis JSON, se puede encontrar fácilmente documentación detallada sobre el tema, por ejemplo en el enlace siguiente: http://www.json.org

Estructuras de datos en JSON

JSON ofrece únicamente tres estructuras de datos:
   
- Pares Nombre/Valor. Un ejemplo sería:
   
            {
                "nombre" : "nombre11"
            }

           
- Objetos. Son colecciones desordenadas de pares Nombre/Valor. Un ejemplo sería:
       
            {
                "contacto" : {
                    "nombre" : "nombre11",
                    "apellido1" : "apellido11",
                    "apellido2" : "apellido21",
                    "email" : "11111111@gmail.com"
                }
            }

           
- Arrays. Son colecciones ordenadas de objetos. Un ejemplo sería:
           
            {
                "agenda" : [
                    {"nombre" : "nombre11", "apellido1" : "apellido11", "apellido2" : "apellido21", "email" : "11111111@gmail.com"},
                    {"nombre" : "nombre12", "apellido1" : "apellido12", "apellido2" : "apellido22", "email" : "22222222@gmail.com"},
                    {"nombre" : "nombre13", "apellido1" : "apellido13", "apellido2" : "apellido23", "email" : "33333333@gmail.com"}
                ]
            }

Tipos de datos en JSON

Hasta el momento hemos visto que en la parte derecha de cada par "Nombre/Valor" aparecía siempre una cadena entrecomillada. JSON admite además de cadenas (String) algunos otros tipos de datos:
  • Object
  • Array
  • String
  • Number
  • Boolean
  • null

Comentarios en JSON

JSON no admite comentarios.

Convenciones en el nombrado de atributos

Los nombres de los atributos en JSON (el elemento izquierdo de cada par Nombre/Valor) se escriben en notación "camel case", que es típica también en Java. La idea es escribir los nombres compuestos metiendo mayúscula al comienzo de cada palabra excepto en la primera. Por ejemplo: "telefFijo", "telefMovil", etc.

Esquemas en JSON

Los documentos JSON, como cabría esperar, están basados en esquemas. Los esquemas definen la estructura de los documentos JSON y permiten validarlos. Los esquemas JSON están evolucionando muy rápidamente (la versión 0.4 está publicada en: http://tools.ietf.org/html/draft-zyp-json-schema-04
   
El sitio de internet donde más esquemas JSON hay publicados es: http://json-schema.org, pero visto el número de esquemas ¿estándares? publicados parece que el tema está aún muy verde. Parece que aún está lejos de sustituir al XML en este sentido.

Herramientas para trabajar con JSON

A la hora de empezar a trabajar con JSON pueden ser interesantes las siguientes herramientas:
  • Validador online JSON. Revisar la sintaxis de un documento JSON grande a ojo es verdaderamente pesado, por tanto puede ser interesante utilizar un validador online como el ofrecido en http://www.jsonlint.com para validar nuestros documentos.
  • Editor JSON. Si lo que necesitamos es un editor JSON donde generar documentos o esquemas con cierto nivel de asistencia, una herramienta muy popular es JSONPad, descargable en: http://www.jsonpad.com/en/Home.html. También hay editores online, siendo uno de los más populares: http://jsoneditoronline.org/

JSON y Java

A la hora de programar aplicaciones que generen o traten datos en formato JSON hay muchos API:
  • Jackson. http://jackson.codehaus.org
  • Google GSON. http://code.google.com/p/google-json/
  • SOJO. http://sojo.sourceforge.net/

Aplicativo Java de pruebas

El siguiente proyecto Java muestra un ejemplo de cómo realizar las siguientes operaciones:
  • Crear un documento JSON a partir de un POJO
  • Crear un POJO a partir de un documento JSON
  • Obtener el esquema de un documento JSON
  • Validar un documento JSON contra un esquema   
Se han utilizado las siguientes librerías:

        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-lgpl</artifactId>
            <version>1.9.12</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <type>jar</type>
        </dependency>

El código son dos clases, la clase Contacto.java y la clase TestJSON.java:
   
package es.ine.sgtic.pojo;

public class Contacto {

    private String nombre;
    private String apellido1;
    private String apellido2;
    private String telefFijo;
    private String telefMovil;
    private String email;

    public Contacto(){
        //Constructor que precisa JSON
    }
    public Contacto(final String nombre,
            final String apellido1,
            final String apellido2,
            final String telefFijo,
            final String telefMovil,
            final String email) {
        this.nombre = nombre;
        this.apellido1 = apellido1;
        this.apellido2 = apellido2;
        this.telefFijo = telefFijo;
        this.telefMovil = telefMovil;
        this.email = email;
    }
    public String getNombre() {
        return nombre;
    }
    public void setNombre(final String nombre) {
        this.nombre = nombre;
    }
    public String getApellido1() {
        return apellido1;
    }
    public void setApellido1(final String apellido1) {
        this.apellido1 = apellido1;
    }
    public String getApellido2() {
        return apellido2;
    }
    public void setApellido2(final String apellido2) {
        this.apellido2 = apellido2;
    }
    public String getTelefFijo() {
        return telefFijo;
    }
    public void setTelefFijo(final String telefFijo) {
        this.telefFijo = telefFijo;
    }
    public String getTelefMovil() {
        return telefMovil;
    }
    public void setTelefMovil(final String telefMovil) {
        this.telefMovil = telefMovil;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(final String email) {
        this.email = email;
    }

    public boolean equals(Contacto cto) {
        return this.nombre.equals(cto.getNombre()) &&
                this.apellido1.equals(cto.getApellido1()) &&
                this.apellido2.equals(cto.getApellido2()) &&
                this.telefFijo.equals(cto.getTelefFijo()) &&
                this.telefMovil.equals(cto.getTelefMovil()) &&
                this.email.equals(cto.getEmail());
    }
}


package es.ine.sgtic.pojo;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.codehaus.jackson.schema.JsonSchema;

public class TestJSON {

    private static Log log = LogFactory.getLog(TestJSON.class);
   
    public static void main (String args[]) 

                       throws JsonGenerationException, 
                       JsonMappingException, IOException {

        //Instanciamos el convertidor Java-JSON (funcionaría en ambos sentidos
        final ObjectMapper mapper = new ObjectMapper();

        //Ajustamos el convertidor para que indente las cadenas JSON genedaras
        mapper.configure(Feature.INDENT_OUTPUT, true);

        //Montamos un pojo de la clase Contacto
        final Contacto cto1 = new Contacto("nombre","apellido1","apellido2",

                                  "123456789","987654321","egdp1970@gmail.com");

        log.trace("Generamos el String cadenaJSON asociado al objeto cto1");
        String cadenaJSON = generaNotacionJSON(mapper, cto1);
        log.trace(cadenaJSON);
        log.trace("----------------------------------------------------");
       
        log.trace("Generamos un objeto cto2 a partir del String cadenaJSON");
        final Contacto cto2 = (Contacto)generaPojojava(mapper, cadenaJSON, cto1.getClass());
        log.trace("Comparamos el objeto cto1 y el objeto cto2");
        log.trace("cto1.equals(cto2)-->"+cto1.equals(cto2));
        log.trace("----------------------------------------------------");

        log.trace("Generamos String cadenaJSON asociado al objeto cto2");
        cadenaJSON = generaNotacionJSON(mapper, cto2);
        log.trace(cadenaJSON);
        log.trace("----------------------------------------------------");

        log.trace("Generamos el String schemaJSON asociado al objeto cto1");
        final String schemaJSON = generaSchemaJSON(mapper, cto1.getClass());
        log.trace(schemaJSON);
        log.trace("----------------------------------------------------");

        log.trace("Validamos el String cadenaJSON contra el esquema schemaJSON");
        log.trace("validaCadenaJSON()-->"+validaCadenaJSON(mapper, cadenaJSON));
        log.trace("----------------------------------------------------");
       
        log.trace("Validamos el String cadenaJSON_ERR contra el esquema schemaJSON");
        final String cadenaJSON_ERR = cadenaJSON + "}";
        log.trace("validaCadenaJSON()-->"+validaCadenaJSON(mapper, cadenaJSON_ERR));
        log.trace("----------------------------------------------------");
    }

    private static String generaNotacionJSON(final ObjectMapper mapper, 

                                             Object obj) 
                                              throws JsonGenerationException, 
                                              JsonMappingException, 
                                              IOException{

        //Convertimos el objeto Java en notación JSON
        final Writer writer = new StringWriter();
        mapper.writeValue(writer, obj);

        //Devolvemos la cadena JSON
        return writer.toString();
    }

    private static Object generaPojojava(final ObjectMapper mapper, 

                                         final String cadenaJSON, 
                                         final Class<?> clazz) 
                                              throws JsonGenerationException, 
                                              JsonMappingException, 
                                              IOException{

        //Convertimos la notación JSON en objeto Java
        return mapper.readValue(cadenaJSON, clazz);
    }

    private static String generaSchemaJSON(final ObjectMapper mapper, 

                                           final Class<?> clazz)                                                                      throws JsonGenerationException, 
                                              JsonMappingException, 
                                              IOException{

        //Generamos el schema JSON para el pojo
        final SerializationConfig cfg = mapper.getSerializationConfig();
        final JsonSchema jsonSchema = mapper.generateJsonSchema(clazz,cfg);
        return jsonSchema.toString();
    }

    private static boolean validaCadenaJSON(final ObjectMapper mapper, 

                                            final String cadenaJSON){

        boolean valido = false;
        try {
            final JsonParser parser = mapper.getJsonFactory().createJsonParser(cadenaJSON);
            while (parser.nextToken() != null) {
            }
            valido = true;
        } catch (JsonParseException e) {
           
//log.error(e.getMessage(),e);
        } catch (IOException e) {
           
//log.error(e.getMessage(),e);
        }

        return valido;
    }
}