Páginas

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;
    }
}

lunes, 4 de febrero de 2013

Primeros pasos con Apache Lucene

Introducción

Lucene es un motor de búsqueda de Apache. Está implementado en Java (http://lucene.apache.org) y es usado en multitud de gestores de contenido.

Lucene indexa texto, no trabaja directamente sobre ningún formato de fichero concreto. Por tanto, para indexar "documentos",  previamente deberá extraerse su contenido con las librerías adecuadas.

Dado cualquier texto, Luceneindexa por defecto las primeras 1000 palabras.

Algunas librerías típicas para menejar los documentos más comunes son:

    PDF. JPedal (http://www.jpedal.org/).
    Microsoft Word, Excel, Visio y Power Point. Apache POI (http://poi.apache.org/)
    OpenOffice. LIUS (http://www.bibl.ulaval.ca/lius/).
    RTF. Swing RTFEditorKit class.

Una vez indexado el contenido de un "documento", se podrán efectuar búsquedas sobre los índices generados. Al efectuarse las búsquedas sobre los índices estas son siempre más rápidas que búsquedas sobre los "documentos".

Componentes de Lucene

En Lucene trabaja con tres conceptos esenciales:
  • Documentos. Un documento, para Lucene, es una tupla de N campos. Cada campo tiene un nombre y contiene cierto tipo de información.
  • Campos. Es cada una de las categorías de información que componen el documento.
  • Querys. Para realizar las búsquedas en el índice, Lucene ofrece un lenguaje de consultas, que permite especificar en qué campos buscar, admite comodines, admite AND, OR y NOT, admite búsquedas por proximidad, etc.
Veamos por ejemplo un par de "documentos":
  • Agenda. En una agenda de contactos, un documento sería cada una de las fichas de la agenda. Los campos serían, por ejemplo, el nombre, el primer apellido, el segundo apellido, el teléfono móvil, el teléfono fijo, etc
  • Biblioteca. En una biblioteca, un documento sería cada uno de los libros. Los campos serían, por ejemplo: título, autor, estantería, contenido, ISBN, etc.
Veamos por ejemplo algunas querys en el índice de la biblioteca:
  • Lista de libros donde el autor sea "Phillip"
        autor:Phillip
  • Lista de libros donde el autor sea "Philip K Dick"
        autor:"Philip K Dick"
  • Lista de libros donde el autor sea "Philip K Dick", en el contenido del libro aparezca "ovejas"
        autor:"Philip K Dick" AND contenido:ovejas

Apliación Java de ejemplo

Hemos montado un proyecto Maven2 con las siguientes dependencias:

  <dependencies>
      <dependency>
          <groupId>org.apache.lucene</groupId>
          <artifactId>lucene-core</artifactId>
          <version>4.0.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.lucene</groupId>
          <artifactId>lucene-queryparser</artifactId>
          <version>4.0.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.lucene</groupId>
          <artifactId>lucene-analyzers-common</artifactId>
          <version>4.0.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-io</artifactId>
          <version>1.3.2</version>
      </dependency>
  </dependencies>


Adjuntamos un ejemplo en Java donde se montará el índice de una biblioteca, añadiendo varios documentos y efectuando algunas búsquedas.

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

public class AppTest {

    public static void main(String[] args) throws IOException, ParseException, InvalidTokenOffsetsException {

        //Fijamos el analizador para tokenizar, indezar y buscar en textos
        final StandardAnalyzer analizador = new StandardAnalyzer(Version.LUCENE_40);

        //Creamos el indice en memoria RAM 

        final Directory indice = new RAMDirectory();
        final IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_40, analizador);

        //Montamos el generador del índice
        final IndexWriter iw = new IndexWriter(indice, config);

        //Nuestros documentos serán libros con tres campos: titulo, autor y contenido.
        //Añadimos un par de libros, los ficheros txt con el contenido de los mismos están en el classpath
        addLibro(iw, "El ingenioso hidalgo Don Quijote de la Mancha", "Miguel de Cervantes y Saavedra", new File("Quijote.txt"));
        addLibro(iw, "El Buscón", "Francisco Gómez de Quevedo y Santibáñez Villegas", new File("Buscon.txt"));
 

        //Cerramos el generador de índice
        iw.close();

        //Lanzamos algunas querys
        lanzaConsulta(analizador, indice, "autor:Cervantes");
        lanzaConsulta(analizador, indice, "autor:\"de Cervantes y\"");
        lanzaConsulta(analizador, indice, "autor:Cervan*");
        lanzaConsulta(analizador, indice, "autor:Cervantes AND titulo:hidalgo");
        lanzaConsulta(analizador, indice, "autor:Cervantes OR autor:Quevedo");
        lanzaConsulta(analizador, indice, "(autor:Cervantes OR autor:Quevedo) AND NOT titulo:Quijote");


        //Algunas querys de búsqueda por proximidad, esto es palabras parecidas a la nuestra 
        //pudiendo diferenciase en N caracteres
        lanzaConsulta(analizador, indice, "contenido:Tocoso~1"); //Buscamos a Dulcinea del "Tocoso" sabiendo que es algo parecido
    }

    private static void addLibro(final IndexWriter iw, 

                                                  final String titulo, 
                                                  final String autor, 
                                                  final File f) throws Exception {

        //Montamos el documento
        final Document libro = new Document();

        //Le añadimos el atributo titulo
        libro.add(new TextField("titulo", titulo, Field.Store.YES));

        //Le añadimos el atributo autor
        libro.add(new TextField("autor", autor, Field.Store.YES));

        //Le añadimos el atributo contenido
        libro.add(new TextField("contenido", new FileReader(f)));

        //Le añadimos la ubicacion del fichero
        libro.add(new TextField("fichero", f.getCanonicalPath(), Field.Store.YES));

        //Damos de alta el documento
        iw.addDocument(libro);
    }


    private static void lanzaConsulta(final StandardAnalyzer analizador, 

                                                           final Directory indice, 
                                                           final String query) throws ParseException, IOException {

        //Montamos el parseador de Querys. El segundo parámetro es el campo por defecto donde
        //se realizarán las búsquedas si una query no indica ningún campo concreto
        final QueryParser queryParser = new QueryParser(Version.LUCENE_40, "titulo", analizador);
        final Query q = queryParser.parse(query);

        //Montamos el buscador
        final IndexReader reader = DirectoryReader.open(indice);
        final IndexSearcher searcher = new IndexSearcher(reader);

        //Lanzamos la query
        final int numMaxResults = 10;
        final TopDocs docsEncontrados = searcher.search(q,numMaxResults);   
        System.out.println("Total hits "+docsEncontrados.totalHits);

        //Recuperamos la lista de coincidencias
        final ScoreDoc[] docs = docsEncontrados.scoreDocs;

        //Mostramos los resultados
        System.out.println("-------------------------------------------------------------------");
        System.out.println("Encontradas " + docsEncontrados.totalHits + " coincidencias para '"+query);
        int idDoc;
        int index;
        float score;
        Document doc;
        for(int i=0;i<docs.length;++i) {
            idDoc = docs[i].doc;
            score = docs[i].score;
            index = docs[i].shardIndex;
            doc = searcher.doc(idDoc);
            System.out.println(String.format("%d. [%f] \t %s \t %s",index,score,doc.get("titulo"),doc.get("autor")));
        }
        System.out.println("-------------------------------------------------------------------");
    }
}


Enlaces de interés

http://wiki.apache.org/lucene-java/LuceneFAQ
http://www.lucenetutorial.com/

viernes, 18 de enero de 2013

Instalación y pruebas de Solr 4.0.0 en Tomcat

Introducción

Solr es un motor de búsqueda de código abierto basado en la biblioteca Java del proyecto Lucene. Ejecuta sobre un contenedor de servlets, como por ejemplo Apache Tomcat.

La finalidad de Solr es tener un motor de búsqueda interno en nuestro servidor.

Descarga Apache Solr

http://www.eu.apache.org/dist/lucene/solr/4.0.0/apache-solr-4.0.0.tgz

Instalación de Solr 4.0.0 en Apache Tomcat

Para instalar Solr 4.0.0 en Apache Tomcat los pasos a seguir son los siguientes:

1) Descomprimimos el fichero apache-solr-4.0.0.tgz en alguna ubicación de nuestro disco duro, por ejemplo en /home/xxxxxxx/temp/apache-solr-4.0.0
2) Recuperamos el war del directorio dist/apache-solr-4.0.0.war
3) Renombramos el fichero apache-solr-4.0.0.war a solr.war
4) Movemos el fichero solr.war a la carpeta webapps de nuestro tomcat
5) Solr necesita un directorio de trabajo que podemos colocar en cualquier ubicación, en este ejemplo supondremos /home/xxxxxxx/solr
6) Volcamos en la carpeta anterior el contenido de /home/xxxxxxx/temp/apache-solr-4.0.0/example/solr.
7) Tras este volcado, en nuestra carpeta /home/xxxxxxx/solr tendremos:

drwxr-xr-x 2 xxxxxxx xxxxxxx 4096 sep 22 14:36 bin
drwxr-xr-x 4 xxxxxxx xxxxxxx 4096 ene 18 12:34 collection1
-rw-r--r-- 1 xxxxxxx xxxxxxx 2473 sep 22 14:36 README.txt
-rw-r--r-- 1 xxxxxxx xxxxxxx 2203 sep 22 14:36 solr.xml
-rw-r--r-- 1 xxxxxxx xxxxxxx  501 sep 22 14:36 zoo.cfg


8) Ahora arrancamos el Tomcat, con lo que se despliega automáticamente el fichero solr.war que habíamos ubicado en la carpeta webapps del Tomcat en el paso 4). Se generará la carpeta webapps/solr.
9) Ahora paramos el Tomcat.
10) Editamos el fichero webapps/solr/WEBINF/web.xml, sustituyendo:

  <!-- People who want to hardcode their "Solr Home" directly into the
       WAR File can set the JNDI property here...
   -->
  <!--
    <env-entry>
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>/put/your/solr/home/here</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>
   -->

por:

  <!-- People who want to hardcode their "Solr Home" directly into the
       WAR File can set the JNDI property here...
   -->

    <env-entry>
       <env-entry-name>solr/home</env-entry-name>
       <env-entry-value>/home/
xxxxxxx/solr</env-entry-value>
       <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>


11) Arrancamos Tomcat y ya podríamos acceder a la página de inicio del portal, http://localhost:8080/solr/admin

Utilización de Solr

Una vez instalado Solr, podemos subir y buscar documentos fácilmente desde Java. Necesitaremos las librerías:

  <dependencies>
      <dependency>
          <groupId>org.apache.solr</groupId>
          <artifactId>solr-common</artifactId>
          <version>1.3.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.solr</groupId>
          <artifactId>solr-core</artifactId>
          <version>1.4.0</version>
      </dependency>
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>1.7.2</version>
      </dependency>
  </dependencies>


El código de la clase ejemplo sería:

import java.io.IOException;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
import org.apache.solr.client.solrj.impl.XMLResponseParser;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;

public class AppTest {

    /**
     * URL del servidor Solr
     */
    private static final String URL_SOLR = "http://localhost:8080/solr";

    public static void main (String args[]) throws SolrServerException, IOException {
   
        //Conectamos con el servidor Solr
        final CommonsHttpSolrServer serverSolr = new CommonsHttpSolrServer(URL_SOLR);
        serverSolr.setParser(new XMLResponseParser());
       
        //Creamos un par de documentos
        creaDocQuijote(serverSolr);
        creaDocBuscon(serverSolr);
       
        //Buscamos documentos por nombre
        buscaDocNombre(serverSolr, "hidalgo");
       
        //Buscamos documentos por contenido
        buscaDocContenido(serverSolr, "Mancha");
       
        //Borramos todo el contenido del servidor (por aquello de dejarlo limpio y poder repetir las inserciones)
        serverSolr.deleteByQuery("*:*");
    }
   
    private static void creaDocQuijote(final CommonsHttpSolrServer serverSolr) throws SolrServerException, IOException {
       
        final SolrInputDocument doc = new SolrInputDocument();
        doc.addField("id", "000001");
        doc.addField("name", "El ingenioso hidalgo don Quijote.txt");
        doc.addField("text", "En un lugar de la Mancha de cuyo nombre");
        serverSolr.add(doc);
        serverSolr.commit();
    }
   
    private static void creaDocBuscon(final CommonsHttpSolrServer serverSolr) throws SolrServerException, IOException {
       
        final SolrInputDocument doc = new SolrInputDocument();
        doc.addField("id", "000002");
        doc.addField("name", "Buscón.txt");
        doc.addField("text", "Yo, señora, soy de Segovia. Mi padre se llamó Clemente Pablo");
        serverSolr.add(doc);
        serverSolr.commit();
    }
   
    private static void buscaDocNombre(final CommonsHttpSolrServer serverSolr, final String nombre) throws SolrServerException{
       
        final SolrQuery query = new SolrQuery();
        query.setQuery("name:quijote.txt");
        final QueryResponse rsp = serverSolr.query(query);
        final SolrDocumentList docList = rsp.getResults();
        System.out.println("Encontrados "+docList.size()+" documentos con el nombre '"+nombre+"'");
    }
   
    private static void buscaDocContenido(final CommonsHttpSolrServer serverSolr, final String contenido) throws SolrServerException {
       
        final SolrQuery query = new SolrQuery();
        query.setQuery("text:Mancha");
        final QueryResponse rsp = serverSolr.query(query);
        final SolrDocumentList docList = rsp.getResults();
        System.out.println("Encontrados "+docList.size()+" documentos con el contenido '"+contenido+"'");
    }
}


Enlaces de interés

http://stackoverflow.com/questions/10026014/apache-solr-configuration-with-tomcat-6-0
http://www.solrtutorial.com/

sábado, 12 de enero de 2013

Primeros pasos con Apache Velocity

En esta entrada vamos a detallar un ejemplo de uso de Apache Velocity. Esta herramienta nos permite generar fácilmente documentos xml, html, sql, csv, txt, etc.

Apache Velocity renderiza una plantilla con ciertos datos. Las plantillas deberán ser generadas previamente en base al lenguaje VTL (Velocity Template Language).

VTL nos permite generar informes muy grandes con poco esfuerzo.

Supongamos que nos interesa enviar el informe siguiente por correo electrónico:

+------+------------+--------+
| Año  | Trimestre  | Ventas |
+------+------------+--------+
| 2005       T1         1000 |
| 2005       T2          200 |
| 2005       T3           50 |
| 2005       T4            5 |
+------+------------+--------+


Como primera aproximación montaríamos una plantilla simple, donde las líneas de contenido fijo aparecen tal cual, mientras que en aquellas líneas donde hay conenido variable (año, trimestre y unidades) hemos insertado unas variables  respetando los espacios en blanco que mantienen el encolumnado.

Las diferentes líneas del informe se generan recorriendo los elementos de la lista $listaVentas, cada elemento de la lista se carga en la variable $venta, y cada venta tiene dos atributos: $venta.trim y $venta.unidaddes.

#***************
Plantilla simple
***************#
+------+------------+--------+
| Año  | Trimestre  | Ventas |
+------+------------+--------+
#foreach($venta in $listaVentas)
| $anno       $venta.trim         $venta.unidades |
#end
+------+------------+--------+


Seguidamente montaríamos el código java, en donde cabe destacar que iniciamos el motor velocity fijando el encoding tanto de la plantilla de entrada como del documento renderizado a generar:

package org.edu.velocity.app;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

public class Test {

    public static void main(String[] args) {

        //Instanciamos el motor velocity
        final VelocityEngine ve = new VelocityEngine();
       
        //Definimos el encoding de entrada y salida
        final Properties props = new Properties();
        props.put("output.encoding","UTF-8");
        props.put("input.encoding","UTF-8");
       
        //Inicializamos el motor velocity con el encoding deseado
        ve.init(props);
       
        //Cargamos la plantilla
        final Template t = ve.getTemplate("informe-simple.vm");
        //final Template t = ve.getTemplate("informe-alineado.vm");
       
        //Preparamos los datos
        final List<Object> listaVentas = new ArrayList<Object>();
        Map<String, String> map = new HashMap<String, String>();
        map.put("trim", "T1");
        map.put("unidades", "1000");
        listaVentas.add(map);
        map = new HashMap<String, String>();
        map.put("trim", "T2");
        map.put("unidades", "200");
        listaVentas.add(map);
        map = new HashMap<String, String>();
        map.put("trim", "T3");
        map.put("unidades", "50");
        listaVentas.add(map);
        map = new HashMap<String, String>();
        map.put("trim", "T4");
        map.put("unidades", "5");
        listaVentas.add(map);
       
        //Creamos el contexto y le metemos los datos
        final VelocityContext context = new VelocityContext();
        context.put("listaVentas", listaVentas);
        context.put("anno", "2005");
       
        //Renderizamos la plantilla
        final StringWriter writer = new StringWriter();
        t.merge( context, writer );
       
        //Visualizamos la plantilla
        System.out.println(writer.toString());

}


Al ejecutar este código tendríamos la siguiente salida:

+------+------------+--------+
| Año  | Trimestre  | Ventas |
+------+------------+--------+
| 2005       T1         1000 |
| 2005       T2         200 |
| 2005       T3         50 |
| 2005       T4         5 |
+------+------------+--------+


La salida es casi lo que queríamos, pero al variar la longitud de los números de la última columna, tiene un aspecto incorrecto. Vamos a reajustar la plantilla con un control del tamaño de la variable $venta.unidades. Con lo que la plantilla alineada quedaría:

#*****************
Plantilla alineada
*****************#
+------+------------+--------+
| Año  | Trimestre  | Ventas |
+------+------------+--------+
#foreach($venta in $listaVentas)
#if($venta.unidades.length()==4)
| $anno       $venta.trim         $venta.unidades |
#{elseif}($venta.unidades.length()==3)
| $anno       $venta.trim          $venta.unidades |
#{elseif}($venta.unidades.length()==2)
| $anno       $venta.trim           $venta.unidades |
#{else}
| $anno       $venta.trim            $venta.unidades |
#end
#end
+------+------------+--------+


Si ejecutamos de nuevo el informe tendremos la salida corregida:

+------+------------+--------+
| Año  | Trimestre  | Ventas |
+------+------------+--------+
| 2005       T1         1000 |
| 2005       T2          200 |
| 2005       T3           50 |
| 2005       T4            5 |
+------+------------+--------+


Librerías.

Para lanzar este ejemplo hemos necesitado las librerías:

velocity-1.7.jar
commons-lang-2.4.jar
commons-collections-3.2.1.jar

Enlaces.

Más información sobre Apache Velocity y VTL en:

http://velocity.apache.org/
http://velocity.apache.org/engine/devel/vtl-reference-guide.html

Conclusiones.

Para generar documentos xml, html, sql, csv y txt, es una herramienta muy potente. La única limitación que le encuentro (sin haberle dedicado mas de una hora al tema), es que si pretendemos generar txt habrá que limitarse a cosas muy simples, dado que en cuanto el documento objetivo es un poco mas complejo la cosa se complica.

No quiero ni pensar qué habría que meter en VTL para generar un informe con varias columnas numéricas que tuviesen que salir alineadas como en el siguiente ejemplo:

+------+-----------+----------+---------+-------+
| Año  | Trimestre | Entradas | Salidas | Stock |
+------+-----------+----------+---------+-------+

| 2005 |     T1    |     1000 |     100 |   900 |
| 2005
|     T2    |      200 |      50 |   150 |
| 2005 |     T3    |       50 |      20 |    30 |
| 2005 |     T4    |        5 |       3 |     2 |
+------+-----------+----------+---------+-------+


La cosa también sería muy compleja si alguien quiere que se generen documentos en txt con justificación completa (es un ejemplo muy rebuscado, me doy cuenta), similares al siguiente ejemplo:

Estimado Juan Español Español.

Nos dirigimos a  vd.  para comunicarle que el
día  17 de  Abril de 2013 a  transferiremos a
la cuenta 0182-0098-XX-XXXXXXXXXX la cantidad
de  1.355,22€ liquidando de ese modo la deuda
pendiente.

En Madrid a 01 de Enero de 2013.

Supongo que las librerías velocity-tools y .

miércoles, 26 de diciembre de 2012

Novedades en Java 7


Java 7 SE presenta novedades a tres niveles: APIs, lenguaje y JVM

1. Novedades en las APIs

1.1) Swing.
  • Se facilita la mezcla de AWT (componentes pesados) con Swing (componentes ligeros).
  • Se posibilitan ventanas con transparencias y con formas no cuadradas.
  • En el componente JColorChooser se habilita la posibilidad de seleccionar todo, saturación y luminusidad (Hue-Saturation-Luminance).
  • Aparece JLayer como decorador para componentes Swing.
  • La apariencia Nimbus se mueve de paquete.
1.2) IO y NIO
  • Gestionar un ZIP como si fuese un file system.
  • Las clases del paquete java.nio.* ofrecen mátodos más intuitivos para la gestión de ficheros y del sistema de archivo.
1.3) Seguridad
  • Soporta TLS 1.1 y TLS 1.2
  • Soporta renegociacion TLS (RFC 5746)
  • Ofrece un mecanismo para rechazar el uso de ciertos algoritmos (los considerados inseguros) en el procesado de rutas de certificación y en el handshaking TLS.
1.4) Concurrencia
  • Se ofrece el framework fork/join para facilitar el trabajo en paralelo (multiporcesador) con un pool de workers.
1.5) Rich Internet Applications (RIA)/Deployment
  • Embedding JNLP File in Applet Tag. Hasta la fecha a la hora de desplegar un applet mediante JNLP, el cliente primero se descargaba el fichero JNLP y luego el jar del app,let con sus dependencias. Ahora podemos incluir el fichero JNLP en el propio JAR del applet así se reduce una llamada y la carga es algo más rápida.
  • Deploying without Codebase. Hasta la fecha en los ficheros JNLP se debía especificar el parámetro Codebase, con Java 7 este parámetro es opcional, si no se indica nada Java Web Start asume que el codebase es relativo a la página web desde la que se lanza la aplicación.
  • Handling Applet Initialization Status with Event Handlers. Cualquier llamada desde JavaScript a un método o a una variable de un applet queda bloqueada hasta que termina el método init() del applet, como además JavaScript no es multihilo, la página web puede parecer congelada durante algunos segundos. Con Java 7 SE, podemos controlar desde JavaScript el estado de carga del applet, si el applet no está cargado podemos registrar los eventos para que se lancen cuando termine de cargarse.
1.6) XML, JAXP, JAXB y JAX-WS
  • Soporta JAXP 1.4.5 (Java API for XML Processing)
  • Soporta JAXB 2.2.3 (Java Architecture for XML Binding)
  • Soporta JAX-WS 2.2.4 (Java API for XML Web Services)
1.7) Internacionalización
  • Soporte de Unicode 6
  • Soporte de para los códigos de monedas ISO 4217
  • Mejoras en el Locale.Category (DISPLAY/FORMAT)
  • Soporte para BCP47 y UTR35 en Locale
   
2. Novedades en el lenguaje

2.1) Podemos manejar literales binarios, hasta ahora sólo se podía con hexadecimales, octales, etc.

        System.out.println("numero "+ 0b1010);

2.2) Se permiten sentencias switch con Strings.

         String dia="lunes";
         switch(dia){
            case "lunes":    System.out.println("Lunes"); break;
            case "martes":     System.out.println("Martes"); break;
            case "miercoles": System.out.println("Miércoles"); break;
            case "jueves":    System.out.println("Jueves"); break;
            case "viernes": System.out.println("Viernes"); break;
            default:        System.out.println("Fin de semana");
        }


2.3) Manejo automático de recursos en try/catch. Se cierran automáticamente archivos, conexiones a bases de datos, etc.

        try(BufferedWriter writer=new BufferedWriter(new FileWriter("/opt/nombreapp/salida/resultado.txt"))) {
               writer.writeln(datos);
        } catch(IOException ex) {
               ex.printStackTrace();
        }


2.4) Captura múltiple de excepciones.

        try {
            System.out.println(10/Integer.parseInt(numero));
        } catch(ArithmeticException | NumberFormatException ex) {
            //manejamos conjuntamente ambas excepciones
        }


2.5) Números con guiones.

        System.out.printf("número: %d%n",1_000);

2.6) Simplificación en el uso de genéricos. Se permiten cosas como:

        List<String> list=new ArrayList<>();

    en vez de:
   
        List<String> list=new ArrayList<String>();

3. Novedades en la JVM

3.1) La JVM soporta lenguajes no java. Java es un lenguaje donde los tipos de datos se verifican en la compilación. Hay otros lenguajes de programación donde los tipos de datos se verifican durante la ejecución, por ejemplo javaScript. Se introduce la nueva instrucción invokedynamic que simplifica y la implementación de compiladores para lenguanes dinámicos en la JVM.

3.2) Garbage-First Collector (o G1 Collector) es una nueva reimplementación del garbage collector orientada a servidores con alto consumo de memoria y multiprocesador. Es interesante su uso en servidores cuando se den una o varias de las siguientes condiciones:
  • Más del 50% de la heap está ocupada con datos activos.
  • Cuando salta el garbage collectior tarda entre 0.5 y 1 seg.
3.3) La JVM HotSpot presenta mejoras de rendimiento:
  • Compilación por capas (Tiered Compilation). Java es "interpretado" en tanto en normalmente ejecuta bytecode y no código máquina, por ello es mas lento. Para solventar esta debilidad, desde la versión 1.3 la JVM HotSpot incorpora JIT         (just-in-time compilation), que compila el bytecode a código máquina antes de ejecutar las clases. Hotspot tiene dos JIT: client JIT y server JIT. El Modo Client del JIT permite arranques más rápidos, pero con menos optimizaciones que el modo Server, que sin embargo arranca más despacio. "Tiered Compilation" tiene lo mejor de los dos compiladores, las optimizaciones del modo Server con la rapidez de arranque del modo Client. Esta mejora puede aplicarse tanto en servidores como en el eclipse.ini.
  • Punteros java comprimidos (Compressed Oops). Los Oop (ordinary object pointer) son los punteros Java a los objetos. Éstos punteros son de 32 o 64 bits según la arquitectura de la máquina. Con 32 bits el tamaño del heap (pila) es menor de 4GB que puede ser poroc para ciertas aplicaciones. Con 64 bits el tamaño del heap es mucho mayor, pero si el aplicativo no necesita realmente ese espacio extra estamos perdiendo memoria. Esta novedad permite usar direcciones de 32 bits aún siendo una arquitectura de 64 bits (una paginación) y ahorrar así memoria. En Java 7 SE, oops es la opción por defecto con JVM de 64-bit si no especificamos un valor para -Xmx o si se especifica un valor inferior a los 32GB.
  • Análisis del alcance de las variables. Otra novedad es el llamado Escape Analysis, mediante esta funcionalidad el compilador JIT de la JVM analiza el alcance de las variables y decide si interesa ubicarlas o no en el heap.
  • NUMA Collector Enhancements. Hoy en día los ordenadores vienen equipados con arquitectura NUMA (Non Uniform Memory Access). Cada rocesador tiene su propia memoria local rápida (como una caché). Java incorpora el NUMA Collector para colocar las variables definidas por cada hilo de un aplicativo en la memoria del procesador que ejecuta ese hilo, así normalmente se aceleran los accesos a memoria y la velocidad de ejecución es mayor.
Notas

Java 7 SE (Standard Edition) se compone de JDK y JRE (como cualquier versión anterior de Java).
  • JRE (Java Runtime Environment). Versión recomendada para la ejecución de aplicaciones Java y applets.
  • JDK (Java Development Kit). Versión recomendada para desarrollar aplicaciones Java y applets. Incorpora el JRE.
Referencias

http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html