¿Cómo leer un xml con Java utilizando Sax?


Procesar xml’s en aplicaciones es una tarea común, en este ejemplo se explicará como utilizar SAX (Simple API for XML), Sax no utiliza el DOM para procesar el xml este utiliza notificaciones (Callbacks) sobre el elemento que se esta procesando en orden iniciando por la parte de arriba del documento y terminando con la etiqueta que cierra el documento.

Archivo a procesar

El archivo que se procesará en este ejemplo será un xml que contiene la información de desayunos en un restaurant, este contiene el nombre, descripción, precio y calorías. La aplicación leerá el xml y generará una lista de objetos java con la información. A continuación se muestra el xml a procesar.

<?xml version="1.0"?>
<breakfast_menu>
	<food>
		<name>Belgian Waffles</name>
		<price>5.95</price>
		<description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
		<calories>650</calories>
	</food>
	<food>
		<name>Strawberry Belgian Waffles</name>
		<price>7.95</price>
		<description>Light Belgian waffles covered with strawberries and whipped cream</description>
		<calories>900</calories>
	</food>
	<food>
		<name>Berry-Berry Belgian Waffles</name>
		<price>8.95</price>
		<description>Light Belgian waffles covered with an assortment of fresh berries and whipped cream</description>
		<calories>900</calories>
	</food>
	<food>
		<name>French Toast</name>
		<price>4.50</price>
		<description>Thick slices made from our homemade sourdough bread</description>
		<calories>600</calories>
	</food>
	<food>
		<name>Homestyle Breakfast</name>
		<price>6.95</price>
		<description>Two eggs, bacon or sausage, toast, and our ever-popular hash browns</description>
		<calories>950</calories>
	</food>
</breakfast_menu>

Crear modelo de la aplicación

Antes de iniciar escribiendo la lógica de la aplicación, iniciaremos creando las clases que nos ayudarán para almacenar la información  y los nombres de los elementos que existe en el xml. Para esto crearemos 2 componentes una enumeración que contendrá el nombre de los elementos del xml y un POJO que contendrá la información que se lea del mismo.

/**
 *
 */
package com.raidentrance.model;

/**
 * @author raidentrance
 *
 */
public enum BreakfastElement {
	FOOD("food"), NAME("name"), PRICE("price"), DESCRIPTION("description"), CALORIES("calories");

	private String name;

	private BreakfastElement(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

}

BreakfastElement contiene los nombres de las etiquetas que nos interesa procesar del xml.

/**
 *
 */
package com.raidentrance.model;

/**
 * @author maagapi
 *
 */
public class Food {

	private String name;
	private double price;
	private String description;
	private int calories;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public int getCalories() {
		return calories;
	}

	public void setCalories(int calories) {
		this.calories = calories;
	}

}

Food contiene los valores que se leerán del xml.

Procesando el xml

Para procesar un xml con SAX es necesario crear un handler, para hacerlo se creará una clase que herede de DefaultHandler y se sobre escribirán  los siguientes métodos:

  • public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
  • public void characters(char[] ch, int start, int length) throws SAXException

  • public void endElement(String uri, String localName, String qName) throws SAXException

  • El método startElement se llamará al inicio de un elemento
  • El método characters  se llamará cuando se encuentre la información dentro de un elemento
  • El método endElement se llamará al final de un elemento
/**
 *
 */
package com.raidentrance.handler;

import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.raidentrance.model.BreakfastElement;
import com.raidentrance.model.Food;

/**
 * @author raidentrance
 *
 */
public class BreakFastHandler extends DefaultHandler {

	private boolean name;
	private boolean price;
	private boolean description;
	private boolean calories;

	private Food currentFood = new Food();
	private List<Food> breakfast = new ArrayList<>();

	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if (qName.equals(BreakfastElement.NAME.getName())) {
			name = true;
		}
		if (qName.equals(BreakfastElement.PRICE.getName())) {
			price = true;
		}
		if (qName.equals(BreakfastElement.DESCRIPTION.getName())) {
			description = true;
		}
		if (qName.equals(BreakfastElement.CALORIES.getName())) {
			calories = true;
		}
	}

	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		if (name) {
			currentFood.setName(new String(ch, start, length));
			name = false;
		}
		if (price) {
			currentFood.setPrice(Double.parseDouble(new String(ch, start, length)));
			price = false;
		}
		if (description) {
			currentFood.setDescription(new String(ch, start, length));
			description = false;
		}
		if (calories) {
			currentFood.setCalories(Integer.parseInt(new String(ch, start, length)));
			calories = false;
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if (qName.equals(BreakfastElement.FOOD.getName())) {
			breakfast.add(currentFood);
			currentFood = new Food();
		}
	}

	public List<Food> getBreakfast() {
		return breakfast;
	}

}

La clase BreakFastHandler implementa la lógica necesaria para leer los elementos del xml, crear POJO’s con la información y agregarlos a una lista con la información.

Creando el parser

El último paso para mostrar la información es crear un parser el cual invocará el handler que creamos y desplegará la respuesta en la consola.

/**
 *
 */
package com.raidentrance.parser;

import java.io.IOException;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;

import com.raidentrance.handler.BreakFastHandler;
import com.raidentrance.model.Food;

/**
 * @author maagapi
 *
 */
public class BreakfastParser {
	public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		SAXParser saxParser = factory.newSAXParser();
		BreakFastHandler handler = new BreakFastHandler();
		saxParser.parse("src/main/resources/menu.xml", handler);
		List<Food> list = handler.getBreakfast();
		for (Food food : list) {
			System.out.println("Name: " + food.getName());
			System.out.println("Description: " + food.getDescription());
			System.out.println("Price: " + food.getPrice());
			System.out.println("Calories: " + food.getCalories());
			System.out.println("---------------------------------------------------------------------------------------------");
		}
	}
}

Salida

A continuación se muestra la salida al ejecutar la aplicación:

Name: Belgian Waffles
Description: Two of our famous Belgian Waffles with plenty of real maple syrup
Price: 5.95
Calories: 650
---------------------------------------------------------------------------------------------
Name: Strawberry Belgian Waffles
Description: Light Belgian waffles covered with strawberries and whipped cream
Price: 7.95
Calories: 900
---------------------------------------------------------------------------------------------
Name: Berry-Berry Belgian Waffles
Description: Light Belgian waffles covered with an assortment of fresh berries and whipped cream
Price: 8.95
Calories: 900
---------------------------------------------------------------------------------------------
Name: French Toast
Description: Thick slices made from our homemade sourdough bread
Price: 4.5
Calories: 600
---------------------------------------------------------------------------------------------
Name: Homestyle Breakfast
Description: Two eggs, bacon or sausage, toast, and our ever-popular hash browns
Price: 6.95
Calories: 950
---------------------------------------------------------------------------------------------

Puedes encontrar el código completo del ejemplo en el siguiente enlace: https://github.com/raidentrance/sax-example

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Plugins de Maven más comunes en Español


En este post se explicará de forma simple los plugins más comunes en Maven.

Apache maven compiler plugin

El compiler plugin de apache es utilizado para compilar el código fuente del proyecto.

¿Por qué es importante?

  • La configuración default del código fuente es 1.5, de tal modo que si tu aplicación utiliza cualquier novedad de java 1.6 o superior el código fuente no compilará.
  • No importa si en las variable de entorno del equipo se utiliza java 1.8, si el maven compiler plugin no está definido, Maven utilizará java 1.5 para compilarlo.
  • Puedes utilizarlo para forzar a tu proyecto a utilizar una versión de Java a la que se tiene configurada en el equipo.

¿Qué errores puede producir?

multi-catch statement is not supported in -source 1.5 [ERROR] (use -source 7 or higher to enable multi-catch statement)
diamond operator is not supported in -source 1.5 (use -source 7 or higher to enable diamond operator)

¿Cómo utilizarlo?

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

Maven Jar plugin

El plugin provee la capacidad de construir Jars.

¿Por qué es importante?

  • Permite personalizar el archivo Manifest de la aplicación que se está generando
  • Permite incluir o excluir contenido del jar que se está generando
  • Permite hacer un Jar ejecutable, para esto se debe especificar la clase principal de la aplicación.

¿Qué errores puede producir?

no hay ningún atributo de manifiesto principal en application-1.jar

¿Cómo utilizarlo?

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-jar-plugin</artifactId>
	<configuration>
		<archive>
			<manifest>
				<addClasspath>true</addClasspath>
				<mainClass>com.raidentrance.MainClass</mainClass>
			</manifest>
		</archive>
	</configuration>
</plugin>

Maven assembly plugin

El assembly plugin fue creado para permitir a los usuarios crear un un jar con sus dependencias, módulos, documentación y otros archivos.

¿Por qué es importante?

  • Si se desea ejecutar un jar, se debe recordar que este requiere de todas sus dependencias.

¿Qué errores puede producir?

java.lang.NoClassDefFoundError

¿Cómo utilizarlo?

<plugin>
	<artifactId>maven-assembly-plugin</artifactId>
	<configuration>
		<descriptorRefs>
			<descriptorRef>jar-with-dependencies</descriptorRef>
		</descriptorRefs>
		<archive>
			<manifest>
				<mainClass>com.raidentrance.MainClass</mainClass>
			</manifest>
		</archive>
	</configuration>
	<executions>
		<execution>
			<id>make-assembly</id>
			<phase>package</phase>
			<goals>
				<goal>single</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Nota: en este plugin se puede definir de igual forma la clase principal que ejecutará la aplicación

Apache Maven dependency plugin

El dependency plugin provee la capacidad de manipular artefactos. Este puede ayudar a copiar y desempaquetar artifacts de repositorios locales y remotos a un lugar específico.

¿Por qué es importante?

  • Analiza las dependencias del proyecto y determinar cuales son : utilizadas y declaradas, utilizadas y no declaradas, no usadas y declaradas.

Ejemplo

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<id>analyze</id>
			<goals>
				<goal>analyze-only</goal>
			</goals>
			<configuration>
				<failOnWarning>true</failOnWarning>
			</configuration>
		</execution>
	</executions>
</plugin>

Salida de ejemplo:

[WARNING] Unused declared dependencies found:
[WARNING]    org.slf4j:slf4j-log4j12:jar:1.7.21:compile
[WARNING]    org.springframework:spring-beans:jar:4.0.3.RELEASE:compile
  • Copia las dependencias a un directorio diferente, un uso común es mover algunas dependencias a un directorio lib.

Ejemplo

<plugin>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<phase>install</phase>
			<goals>
				<goal>copy-dependencies</goal>
			</goals>
			<configuration>
				<outputDirectory>${project.build.directory}/lib</outputDirectory>
			</configuration>
		</execution>
	</executions>
</plugin>
  • Descomprime el proyecto en un directorio específico a demás de incluir o excluir archivos.

Ejemplo

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<id>unpack-dependencies</id>
			<phase>package</phase>
			<goals>
				<goal>unpack-dependencies</goal>
			</goals>
			<configuration>
				<includes>**/*.class</includes>
				<excludes>**/*.properties</excludes>
				<outputDirectory>${project.build.directory}/alternateLocation</outputDirectory>
				<overWriteReleases>false</overWriteReleases>
				<overWriteSnapshots>true</overWriteSnapshots>
			</configuration>
		</execution>
	</executions>
</plugin>

Jetty plugin

El plugin de jetty es muy utilizado para un rápido desarrollo y pruebas durante el desarrollo de aplicaciones web que requieren un contenedor web.

¿Por qué es importante?

  • Es posible iniciar el contenedor de jetty utilizando un goal de maven mvn jetty:run.
  • Es posible desplegar los cambios realizados en el código sin la necesidad de reiniciar el contenedor.
  • Incrementa la productividad del equipo al realizar los hot deploys.

Ejemplo

<plugin>
	<groupId>org.eclipse.jetty</groupId>
	<artifactId>jetty-maven-plugin</artifactId>
	<version>9.2.11.v20150529</version>
</plugin>

Una vez colocado este plugin en la aplicación ejecutar el goal mvn jetty:run. Con esto se ejecutará el contenedor en el puerto 8080 utilizando el contexto ROOT.

Agregando un context path, intervalo de verificación y puerto.

<plugin>
	<groupId>org.eclipse.jetty</groupId>
	<artifactId>jetty-maven-plugin</artifactId>
	<configuration>
		<scanIntervalSeconds>10</scanIntervalSeconds>
		<webApp>
			<contextPath>/raidentrance</contextPath>
		</webApp>
		<httpConnector>
			<port>9999</port>
		</httpConnector>
	</configuration>
</plugin>

Una vez que se ejecute la aplicación el contenedor se desplegará en http://localhost:9999/raidentrance

Jacoco maven plugin

Este plugin es utilizado para generar el code coverage del proyecto.

¿Por qué es importante?

  • Permite conocer la cantidad de código cubierto por pruebas unitarias.
  • Permite conocer las condiciones evaluadas con test unitarios en pruebas unitarias.

Ejemplo

<plugin>
	<groupId>org.jacoco</groupId>
	<artifactId>jacoco-maven-plugin</artifactId>
	<configuration>
		<append>true</append>
	</configuration>
	<executions>
		<execution>
			<goals>
				<goal>prepare-agent</goal>
			</goals>
		</execution>
		<execution>
			<id>post-unit-test</id>
			<phase>test</phase>
			<goals>
				<goal>report</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Para generar los reportes ejecutar el goal de maven mvn site.

Los libros recomendados para este tema son:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com