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();
}
}
}
Páginas
viernes, 26 de abril de 2013
Ejemplo JMS Topic con Spring 2.5 y ActiveMQ
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);
}
}
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
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.
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.
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
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
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:
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:
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>
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).
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.
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.orgEstructuras 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-04El 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
<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;
}
}
Suscribirse a:
Entradas (Atom)




