Spring boot + Profiles (Soporta múltiples entornos)


Uno de los problemas más comunes cuando se desarrollan aplicaciones en cualquier lenguaje es utilizar un solo código que este se ejecute en múltiples entornos (dev, qa y prod). En todos los entornos se utilizarán las mismas configuraciones, lo que cambiará de uno a otro serán los valores asignados a las mismas.

Para soportar esto Spring boot provee perfiles que son diferentes a los perfiles normales de Maven, para saber más sobre ellos re recomendamos el post Perfiles Maven en Español !.

Primeros pasos

Antes de adentrarnos a cómo configurar los perfiles primero existen algunos conceptos que debemos entender:

  • Un perfil se asignará a un entorno de tal modo que si tenemos los entornos, Desarrollo, QA y Producción se deberán crear 3 perfiles.
  • A diferencia de los perfiles normales de Maven, en Spring boot los perfiles se utilizarán en tiempo de ejecución, de este modo no es necesario compilar los proyectos con la bandera -P.
  • Existen diferentes formas de definir un perfil en Maven, en este post se explicarán las 2 principales utilizando archivos properties y utilizando archivos yaml.
  • Para estos ejemplos se tomará como base el proyecto creado en el post Spring Boot + REST Jersey Parte 1.

Definiendo perfiles utilizando archivos properties

Para configurar los perfiles maven utilizando archivos properties es necesario hacer lo siguiente.

1. Agregar un archivo properties por cada perfil que se desea soportar

El primer paso será crear un archivo properties en el folder src/main/resources por cada uno de los entornos a soportar siguiendo la siguiente estructura:

application-${profile}.properties

${profile} Será el nombre de nuestro entorno. Para este ejemplo se crearán los siguientes 3 archivos:

  • application-dev.properties
 com.raidentrance.app.name=Spring boot dev
  • application-qa.properties
 com.raidentrance.app.name=Spring boot qa
  • application-prod.properties
 com.raidentrance.app.name=Spring boot prod

2. Crear un nuevo servicio para mostrar el valor de la propiedad

Una vez definida la nueva propiedad llamada com.raidentrance.app.name el siguiente paso será exponerla en un servicio. Para esto se creará el siguiente servicio:

/**
*
*/
package com.raidentrance.resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* @author maagapi
*
*/
@Component
@Path("/configs")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ProjectConfigurationResource {

@Value("${com.raidentrance.app.name}")
private String appName;

private static final Logger log = LoggerFactory.getLogger(UserResource.class);

@GET
@Path("/appName")
public Response getAppName() {
log.info("Getting project configuration");
return Response.ok(appName).build();
}
}

El servicio anterior mostrará en la respuesta del endpoint GET /configs/appName el nombre definido en nuestro archivo de configuración.

3. Registrar el servicio en Jersey

Recordemos que siempre que se crea un servicio nuevo en Jersey lo debemos agregar en nuestra clase JerseyConfig como se muestra a continuación:

/**
*
*/
package com.raidentrance.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.ProjectConfigurationResource;
import com.raidentrance.resource.UserResource;

/**
* @author raidentrance
*
*/
@Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(UserResource.class);
register(ProjectConfigurationResource.class);
}
}

Recordemos que el servicio UserResource fue el que ya existía en el ejemplo y no es necesario para la explicación de perfiles.

4. Probado todo junto

Una vez que ya se configuró todo lo anterior lo único que resta es probarlo, para esto solo es necesario compilar nuestra aplicación como siempre con un simple mvn clean install, lo único que será diferente será al momento de ejecutarlo, para esto se utilizará el siguiente comando:

mvn -Dspring.profiles.active=qa spring-boot:run

Como se puede ver en el comando se ejecutará la aplicación utilizando las configuraciones definidas en el perfil de qa.

Si ejecutamos el endpoint GET /configs/appName la salida será la siguiente:

Spring boot qa

Definiendo perfiles utilizando archivos yaml

Una vez que ya sabemos como definir los perfiles utilizando multiples archivos .properties, el siguiente paso será entender como hacerlo utilizando archivos yaml.

Paso 1 Definir las configuraciones

A diferencia de la configuración utilizando archivos properties, para definir multiples perfiles utilizando archivos yaml solo es necesario crear un solo archivo para todos los perfiles de la aplicación veamos el ejemplo mencionado anteriormente pero utilizando archivos yaml.

spring:
    profiles:
        active: dev
---
spring:
    profiles: dev
com:
    raidentrance:
      app:
        name: Spring boot dev
---
spring:
    profiles: qa
com:
    raidentrance:
      app:
        name: Spring boot qa
---
spring:
    profiles: prod
com:
    raidentrance:
      app:
        name: Spring boot prod

Como se puede observar a diferencia de la definición con archivos properties es posible definir en un solo archivo yaml el perfil por defecto a utilizar y las propiedades para los diferentes entornos.

Los demás componentes del código serán iguales para ambas formas.

Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/spring-boot-example/tree/part7-profiles .

Si te gusta el contenido o tienes preguntas síguenos en nuestras redes sociales: https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.

 

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring boot + Spring JDBC (English version)


In this post we will explain how to access to a database by using Spring boot + Spring JDBC step by step. For this we will use the project Spring Boot + REST Jersey Part 1 as base.

Step 1 : Configure the required dependencies

The first step will be add the required dependencies to the project, in this case we will use 2 spring-boot-starter-jdbc and mysql-connector-java.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
  • spring-boot-starter-jdbc : Contains all the necessary classes that we will use to enable spring jdbc.
  • mysql-connector-java: Contains the mysql driver to connect via JDBC.

Step 2: Create the tables we will use in the database

For this example we will use MySQL as database engine and we will create a database named jdbc_example with the following table:

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);

And with the following data:

INSERT INTO USER (USERNAME,PASSWORD)VALUES('raidentrance','superSecret');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('juan','hola123');

Step 3: Create the class to represent a User

Now we will create a POJO to represent the information stored in the User table.

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

/**
 * @author raidentrance
 *
 */
public class User {
    private Integer id;
    private String user;
    private String password;

    public User() {
    }

    public User(Integer id, String user, String password) {
        super();
        this.id = id;
        this.user = user;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

Step 4: Add the database configuration to the project

The next step is to include the database configuration to the project, in order to do it we will edit the file application.properties with the following information:

spring.datasource.url=jdbc:mysql://localhost:3306/jdbc_example
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Step 5 : Create a DAO (Data access object)

Once Spring has the information to connect to the database the next step is create a data access object, it will be used to execute operations over the User table in the database:

/**
 *
 */
package com.raidentrance.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.ws.rs.core.Response.Status;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.ServiceException;
import com.raidentrance.model.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List<User> findAll() {
        List<User> users = jdbcTemplate.query("select * from user", new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int arg1) throws SQLException {
                User user = new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
                return user;
            }
        });
        return users;
    }

    public User findByUsername(String username) throws ServiceException {
        User user = jdbcTemplate.query(new PreparedStatementCreator() {

            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                PreparedStatement ps = con.prepareStatement("select * from user where username=?");
                ps.setString(1, username);
                return ps;
            }
        }, new ResultSetExtractor<User>() {
            @Override
            public User extractData(ResultSet rs) throws SQLException, DataAccessException {
                if (rs.next()) {
                    User user = new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
                    return user;
                } else {
                    return null;
                }
            }
        });
        if (user != null) {
            return user;
        } else {
            throw new ServiceException(Status.NOT_FOUND.getStatusCode(), "User not found ", 4004);
        }
    }
}

In the previous code we can see the following points :

  • the @Componen annotation: It means that the object will be living in the spring context and we can access to the instance by using the @Autowired annotation.
  • @Autowired JdbcTemplate jdbcTemplate : The JdbcTemplate will use the configuration that we establish in the application.properties file to connect to our database. We use @Autowired to get a reference to the object that is living in the Spring context.
  • public List findAll() : This method will be used to get all the users in the table. As you can see this method receives the sql query that we want to execute and an object that implements the RowMapper interface, this object will be used to transform from a ResultSet to a Java list.
  • public User findByUsername(String username): The method findByUsername will be used to get a user by username. In this example we are using a PreparedStatement to prevent SQL Injection because this sql query receives a parameter. Other important difference is that this method is receiving a ResultSetExtractor instead a RowMapper and the reason to do it is because this method will return a single object in the response.
  • The last point is that we can see that in case that we cant find a user by its username we will throw a ServiceException with a message, code and http status.

Step 6: Using the DAO in our web service

Once we have a DAO created we have to use it in our endpoint, in future posts we will see that is a good practice to separate this logic in a separated service, but for now we will inject the DAO directly in the endpoint UserResource as follow:

/**
 *
 */
package com.raidentrance.resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.raidentrance.dao.UserDao;
import com.raidentrance.model.ServiceException;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    @Autowired
    private UserDao userDao;

    private static final Logger log = LoggerFactory.getLogger(UserResource.class);

    @GET
    public Response getUsers() {
        log.info("Getting users");
        return Response.ok(userDao.findAll()).build();
    }

    @GET
    @Path("/user/{username}")
    public Response getUser(@PathParam("username")String username) throws ServiceException {
        log.info("Getting users");
        return Response.ok(userDao.findByUsername(username)).build();
    }

}

As you can see, to inject the DAO we just need to use the annotation @Autowired because the object lives in the spring context.

Step 7: Testing all together

To execute the application we have to execute the main class as in all the Spring boot applications and access to the following url http://localhost:8080/users, we can see the following output:

Captura de pantalla 2017-09-18 a las 2.20.01 p.m.

If we want to get a single user by using the username we will use the url http://localhost:8080/users/user/raidentrance and it will show the following output:

Captura de pantalla 2017-09-18 a las 2.21.40 p.m.

You can find the complete code in the url https://github.com/raidentrance/spring-boot-example/tree/part6-spring-jdbc .

And if you want to learn more about Spring boot and web services we recommend the following books:

Also you can find version in Spanish of this post here.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring Boot + REST Jersey (Adding Spring data) Part 2


In this post, I will explain in an easy way how to configure and use Spring data with Spring boot. In order to do it we will use a previous example Spring Boot + REST Jersey Part 1.

Step 1 (Including maven dependencies)

Spring boot generated starter dependencies, this dependencies contain all the necessary resources to use each of the spring modules in the easiest way. The versions of the necessary dependencies are not required because they come from the spring boot parent project. To configure Spring Data is necessary add the following dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

Step 2 (Configuring the datasource)

The next step will be create a file named application.properties, in this file we will define the information of the database connection, in this example we will show how to create a datasource for MySQL.

spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_users
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

In the file application.properties is possible to define multiples configurations for spring boot like database information, ports, context path, etc. This file can be a .properties or a .yml file.

Step 3 (Configuring repositories)

Spring Data uses repositories to access to the information, now we have to define the java package that will contain all the repositories, in this example will be com.raidentrance.repositories.

/**
 *
 */
package com.raidentrance.config;

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author raidentrance
 *
 */
@EnableTransactionManagement
@EnableJpaRepositories("com.raidentrance.repositories")
public class SpringDataConfig {

}

Step 4 (Creating the tables and data for the example)

The application will use a table named user with the following structure:

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);

Now we have to insert some sample data:

INSERT INTO USER (USERNAME,PASSWORD)VALUES('raidentrance','superSecret');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('juan','hola123');

Step 6 (Creating JPA entities)

Once we configured Spring Data, created the tables and populated them we have to create a JPA entity to represent the relational model in the domain model by creating the entity User.java.

/**
 *
 */
package com.raidentrance.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * @author raidentrance
 *
 */
@Entity
@Table(name = "USER")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer idUser;

    @Column(name = "USERNAME")
    private String username;

    @Column(name = "PASSWORD")
    private String password;

    private static final long serialVersionUID = -5290198995172316155L;

    public Integer getIdUser() {
        return idUser;
    }

    public void setIdUser(Integer idUser) {
        this.idUser = idUser;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User [idUser=" + idUser + ", username=" + username + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((idUser == null) ? 0 : idUser.hashCode());
        result = prime * result + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (idUser == null) {
            if (other.idUser != null)
                return false;
        } else if (!idUser.equals(other.idUser))
            return false;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }

}

Step 7 (Creating a Spring data repository)

Now we have to create a Spring data repository, this will be used to access to the database:

/**
 *
 */
package com.raidentrance.repositories;

import org.springframework.data.repository.CrudRepository;

import com.raidentrance.entities.User;

/**
 * @author raidentrance
 *
 */
public interface UserRepository extends CrudRepository<User, Integer> {
}

UserRepository is an interface, the most common question is, do I have to create a class that implements the UserRepository interface ? the answer is NO, the only thing that we need to do is to define the interface and Spring Data will create the implementation for it.

Step 8 (Using the JPA Repository)

The last part is to use the repository in the application, in order to do it, we will integrate the component in the web service REST created in the part 1 of the tutorial Spring Boot + REST Jersey Part 1.

/**
 *
 */
package com.raidentrance.resource;

import java.util.ArrayList;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.raidentrance.entities.User;
import com.raidentrance.repositories.UserRepository;
import jersey.repackaged.com.google.common.collect.Lists;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    @Autowired
    private UserRepository userRepository;

    @GET
    public Response getUsers() {
        ArrayList<User> users = Lists.newArrayList(userRepository.findAll());
        return Response.ok(users).build();
    }

}

In order to use the repository created we will use the annotation @Autowired, as you can see the method used to get all the users is findAll(), this method is not defined in the interface created, it is defined in the parent interface named CrudRepository.

Step 9 (Testing all together)

In order to test the project the only thing you need to do is execute the class SpringBootSampleApplication, when it finishes you have to execute the endpoint http://localhost:8080/users.

Output

[
{
idUser: 1,
username: "raidentrance",
password: "superSecret"
},
{
idUser: 2,
username: "john",
password: "smith"
},
{
idUser: 3,
username: "juan",
password: "hola123"
}
]

Next steps

In the following posts we will explain:

  • How to return a DTO instead of a list of entities in the web service REST
  • Add ExceptionMappers for error handling
  • Add support for HATEOAS
  • Add spring security
  • Add logging
  • Add different profiles for different environments

You can find the complete code in the following link https://github.com/raidentrance/spring-boot-example/tree/part2-adding-springdata

If you want to learn more about web services we recommend the following books:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Real time processing of Tweets with apache Storm


In this post I’m going to explain in an easy way how to process information on real time by using Apache Storm.

Before to start

Before to start with the configuration of the project and the explanation about Apache Storm it is necessary to create a developer account on Twitter, for this purpose you need to sign in in the following url: https://apps.twitter.com/ and register an application, once you created the application Twitter will provide you the following information:

oauth.consumerSecret=
oauth.consumerKey=
oauth.accessToken=
oauth.accessTokenSecret=

Configuration

The application will be a Java application created with Maven, so we need to add the following dependencies:

<dependencies>
    <dependency>
        <groupId>org.apache.storm</groupId>
        <artifactId>storm-core</artifactId>
        <version>1.0.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.twitter4j/twitter4j-core -->
    <dependency>
        <groupId>org.twitter4j</groupId>
        <artifactId>twitter4j-core</artifactId>
        <version>4.0.4</version>
    </dependency>
    <dependency>
        <groupId>org.twitter4j</groupId>
        <artifactId>twitter4j-stream</artifactId>
        <version>4.0.4</version>
    </dependency>
</dependencies>

The application will use an api to get information on real time named twitter4j-stream, in order to configure it we will create a file named twitter4j.properties in the folder src/main/resources with the following properties:

oauth.consumerSecret={Tu consumerSecret}
oauth.consumerKey={Tu consumerKey}
oauth.accessToken={Tu accessToken}
oauth.accessTokenSecret={Tu accessTokenSecret}

Apache Storm

Apache storm is a tool that allows you to process information in real time by using the following components:

  • Spout: The spouts are components used to read information from a source of data and emit this information to the components that are going to execute the logic. In this example the source of data will be Twitter and the information to emit will be the tweets related with Java, JavaScript,PHP and Phyton.
  • Bolts: Bolts are components that will receive the information from the spouts and execute the business logic of the application. In this example the bolt will receive all the tweets coming from the spout and will execute the following logic:
    • Receive tweets related with programming languages
    • Validate what king of language is talking
    • Write the tweet in a file depending of the programing language
  • Stream groupings: Part of defining a topology is specifying for each bolt which streams it should receive as input. A stream grouping defines how that stream should be partitioned among the bolt’s tasks. In the example we will specify that our spout will emit the information to our bolt.
  • Topology: All the logic of a real time application will be executed in a topology (spouts+bolts), this is equivalent to aMapReduce job in Hadoop, the difference is that a MapReduce job  will finish and the topology will be running all the time.

Writing the code

The first step is to define an enum with all the supported languages that we will monitor in the social network with the file names that are going to store the tweets.

/**
 *
 */
package com.raidentrance.util;

/**
 * @author alex @raidentrance
 *
 */
public enum Languages {
    JAVA("java", "java.txt"), JAVASCRIPT("javascript", "javascript.txt"), PHP("php", "php.txt"), PYTHON("python",
            "python.txt");

    private String name;
    private String fileName;

    private Languages(String name, String fileName) {
        this.name = name;
        this.fileName = fileName;
    }
    public String getName() {
        return name;
    }
    public String getFileName() {
        return fileName;
    }
}

The next step will be create a file manager, in this example it will write the tweets in the corresponding file.

/**
 *
 */
package com.raidentrance.util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;

import twitter4j.Status;

/**
 * @author alex @raidentrance
 *
 */
public class FileManager implements Serializable {

    private static final long serialVersionUID = -3987517536486344388L;

    public void writeTweet(Status tweet, String fileName) throws IOException {
        File file = new File(fileName);
        if (!file.exists()) {
            file.createNewFile();
        }

        FileWriter fileWritter = new FileWriter(file.getName(),true);
        BufferedWriter bufferWritter = new BufferedWriter(fileWritter);
        bufferWritter.write("\n" + tweet.getText() + "\n Retweet count : " + tweet.getUser().getFollowersCount() + "\n Tweet id "
                + tweet.getId() + "\n User id" + tweet.getUser().getName()
                + "\n----------------------------------------");
        bufferWritter.close();
    }
}

Once we create all our helper classes, we will start with our storm components, the first one will be the spout that will read the information from Twitter:

/**
 *
 */
package com.raidentrance.spout;

import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;

import com.raidentrance.util.Languages;

import twitter4j.FilterQuery;
import twitter4j.StallWarning;
import twitter4j.Status;
import twitter4j.StatusDeletionNotice;
import twitter4j.StatusListener;
import twitter4j.TwitterStream;
import twitter4j.TwitterStreamFactory;

/**
 * @author alex @raidentrance
 *
 */
public class TweetStreamSpout extends BaseRichSpout {

    private SpoutOutputCollector collector;

    private LinkedBlockingQueue<Status> queue;
    private TwitterStream twitterStream;

    private static final long serialVersionUID = 4256154244602991768L;

    public void nextTuple() {
        final Status status = queue.poll();
        if (status == null) {
            Utils.sleep(50);
        } else {
            collector.emit(new Values(status));
        }
    }

    public void open(@SuppressWarnings("rawtypes") Map map, TopologyContext context, SpoutOutputCollector collector) {
        System.out.println("Opening the bolt");
        this.collector = collector;
        this.twitterStream = new TwitterStreamFactory().getInstance();
        this.queue = new LinkedBlockingQueue<>();
        StatusListener listener = new StatusListener() {
            @Override
            public void onStatus(Status status) {
                queue.offer(status);
            }

            @Override
            public void onDeletionNotice(StatusDeletionNotice sdn) {
            }

            @Override
            public void onTrackLimitationNotice(int i) {
            }

            @Override
            public void onScrubGeo(long l, long l1) {
            }

            @Override
            public void onException(Exception e) {
            }

            @Override
            public void onStallWarning(StallWarning warning) {
            }
        };
        twitterStream.addListener(listener);
        FilterQuery filterQuery = new FilterQuery();

        for (Languages language : Languages.values()) {
            filterQuery.track(language.getName());
        }

        twitterStream.filter(filterQuery);
    }

    @Override
    public void activate() {
    };

    @Override
    public void deactivate() {
        twitterStream.cleanUp();
    };

    @Override
    public void close() {
        twitterStream.shutdown();
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("status"));
    }
}

The spout TweetStreamSpout uses the api twitter4j-stream  to receive as a real time stream all the tweets related with the words java, javascript, php and python and it will emit the data using a output variable named status to the bolt.

Once we have the source of data the next step will be create a bolt to execute the business logic of our application:

/**
 *
 */
package com.raidentrance.bolt;

import java.io.IOException;
import java.util.Map;

import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Tuple;

import com.raidentrance.util.FileManager;
import com.raidentrance.util.Languages;

import twitter4j.Status;

/**
 * @author alex @raidentrance
 *
 */
public class TwitterAnalyzerBolt extends BaseRichBolt {

    private FileManager manager = new FileManager();
    private OutputCollector collector;

    private static final long serialVersionUID = 8465078768241865446L;

    @Override
    public void prepare(@SuppressWarnings("rawtypes") Map stormConf, TopologyContext context,
            OutputCollector collector) {
        this.collector = collector;
    }

    @Override
    public void execute(Tuple tuple) {
        final Status tweet = (Status) tuple.getValueByField("status");
        for (Languages language : Languages.values()) {
            if (tweet.getText().toLowerCase().contains(language.getName())) {
                try {
                    manager.writeTweet(tweet, language.getFileName());
                } catch (IOException e) {
                    collector.fail(tuple);
                }
            }
        }

        System.out.println("\n" + tweet.getText() + "\n Retweet count : " + tweet.getUser().getFollowersCount()
                + "\n Tweet id " + tweet.getId() + "\n User id" + tweet.getUser().getName()
                + "\n----------------------------------------");
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    }
}

The bolt TwitterPrinterBolt will get all the information coming from the spout by using the variable status and will execute the logic to save the tweet in the corresponding file

The last step will be create the topology, in this component we will define the spouts and bolts, the relationship between them and the deployment mechanism.

/**
 *
 */
package com.raidentrance.topologies;

import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;

import com.raidentrance.bolt.TwitterAnalyzerBolt;
import com.raidentrance.spout.TweetStreamSpout;

/**
 * @author alex @raidentrance
 *
 */
public class TwitterTopology {
    public static void main(String args[]) {
            TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("twitterSpout", new TweetStreamSpout());
        builder.setBolt("twitterAnalyzerBolt", new TwitterAnalyzerBolt(), 1).shuffleGrouping("twitterSpout");

        Config conf = new Config();
        conf.setDebug(false);

        final LocalCluster cluster = new LocalCluster();
        cluster.submitTopology("twitterTopology", conf, builder.createTopology());
    }
}

To create the topology we will use the class TopologyBuilder and with this we will define the spouts, bolts and relationships.
To define the deployment mechanism we will use for this example the class LocalCluster, it will start storm instance to deploy our application, this class is just for development, for production you have to follow a different process.

Execution the application

Once we wrote all the code the next step will be execute the class TwitterTopology. It will print all the tweets that is processing and generate the files per programming language wi the received tweets.

You can download the complete code in the following link https://github.com/raidentrance/storm-sample.

Sources: http://storm.apache.org

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring Boot + REST Jersey Part 1


In this post we will explain in a simple way the basic configuration of Spring Boot with Jersey, this is the first in a series of posts that will be explaining more advanced topics regarding the previously mentioned technologies.

1.- Configuration:

Configuring Spring Boot is a very simple task, you just need to inherit from the spring boot base project, include the dependencies of the spring modules and configure the plugins to be used in the project.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.0.RELEASE</version>
</parent>

When we inherit from the spring boot parent project, we are inheriting all the versions defined for each component so you don’t need specify the versions in each dependency.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jersey</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
    </dependency>
</dependencies>

As you can see, you only need specify the dependencies but not the versions, they are all specified in the spring-boot-starter-parent.

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

2.- Creating a main class:

Once we have completed our maven settings the next step is to create a main class for our application, this class will be responsible for the initialisation of the container and the project; the default container for the application is Jetty but as it is indicated by the dependencies in our pom.xml file, we will be using Tomcat.

/**
 *
 */
package com.raidentrance;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author raidentrance
 *
 */
@SpringBootApplication
public class SprinBootSampleApplication  {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(SprinBootSampleApplication.class, args);
    }

}

One of the most common questions at this point is “Why are we using a main method if we are creating a web application?” The answer is quite simple, the application is not going to generate a war file but a fat jar, it is known as fat jar because besides the application itself, it includes an embedded container such as a Jetty or Tomcat one. In future posts we will explain how to use this in a production environment.

3.- Writing the REST web service.

To create a REST web service with spring you just have to use the annotations from the Jersey project and mark the classes with @Component to add it to the Spring context.

/**
 *
 */
package com.raidentrance.resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.springframework.stereotype.Component;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    @GET
    public String getUsers() {
     return "{\"username\":\"raidentrance\"}";
    }
}

The following lines are intended to describe the purpose of each annotation used in the present example:

  • @Component: Used to include the class UserResource in the Spring Context.
  • @Path(“users”): Used to define an endpoint where the service is going to be accessed, it is possible to put another annotation at method level to specify more complex endpoints.
  • @Produces(MediaType.APPLICATION_JSON) / @Consumes(MediaType.APPLICATION_JSON): These annotations specify that the endpoints registered on that class will consume and produce JSON files, for this reason the method getUsers() returns a JSON formatted string, it is possible to put these annotations in a method level to modify its behaviour.

4.- Registering the service:

Once we have the service and spring boot has been configured, the missing step is to register the service in Jersey, for this you have to create a class where all the services are going to be registered.

/**
 *
 */
package com.raidentrance.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.UserResource;

/**
 * @author raidentrance
 *
 */
@Component
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig() {
         register(UserResource.class);
    }
}

This class shouldn’t be invoked from anywhere in the application, just by being registered using the @Component annotation and inheriting from ResourceConfig Spring will know which configuration to use to register the services.

5.- Executing and testing.

To test the application, the only thing we need to do is to execute  SpringBootSampleApplication class as a desktop application, or using its jar file with the command:

 java -jar target/jar-name.jar

Output:

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.4.0.RELEASE)

2016-09-02 11:23:28.005 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : Starting SprinBootSampleApplication on m-C02RV1WXG8WP.local with PID 20231 (/Users/maagapi/Documents/Github/spring-boot-example/target/classes started by maagapi in /Users/maagapi/Documents/Github/spring-boot-example)
2016-09-02 11:23:28.008 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : No active profile set, falling back to default profiles: default
2016-09-02 11:23:28.064 INFO 20231 --- [ restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@53294542: startup date [Fri Sep 02 11:23:28 CDT 2016]; root of context hierarchy
2016-09-02 11:23:28.646 INFO 20231 --- [ restartedMain] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2016-09-02 11:23:29.000 INFO 20231 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-09-02 11:23:29.012 INFO 20231 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service Tomcat
2016-09-02 11:23:29.013 INFO 20231 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.4
2016-09-02 11:23:29.075 INFO 20231 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2016-09-02 11:23:29.075 INFO 20231 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1013 ms
2016-09-02 11:23:29.260 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-09-02 11:23:29.261 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2016-09-02 11:23:29.261 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'com.raidentrance.config.JerseyConfig' to [/*]
2016-09-02 11:23:29.441 INFO 20231 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2016-09-02 11:23:29.469 INFO 20231 --- [ restartedMain] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2016-09-02 11:23:29.510 INFO 20231 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-09-02 11:23:29.513 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : Started SprinBootSampleApplication in 2.206 seconds (JVM running for 2.542)

To test the code you should access http://localhost:8080/users in your web browser.

In the following sections we will explain how to add logs, configure spring security, spring data, error handling and deployments to production environments.

If you want to learn more about web services we recommend the following books:

You can find the complete code for this project in the following link:

https://github.com/raidentrance/spring-boot-example

Author: Alejandro Agapito Bautista

Twitter: @raidentrance

Contact: raidentrance@gmail.com

Translated by: Oscar Camacho – melchior01

Contact: adamfirstangel@hotmail.com

Instala Redis y utilízalo con Java en Español


Introducción

Redis es un motor de base de datos en memoria y es utilizado en muchas empresas como base de datos, sistema de cache e incluso como message broker. Soporta estructuras de datos como Strings, hashes, lists, sets, etc. En este blog se explicará como iniciar un servidor de Redis y como conectarse a el utilizando Java.

Iniciando un servidor de Redis

El primer paso es descargar extraer y compilar Redis utilizando los siguientes comandos:

$ wget http://download.redis.io/releases/redis-3.2.9.tar.gz
$ tar xzf redis-3.2.9.tar.gz
$ cd redis-3.2.9
$ make

Una vez que todo se compiló correctamente el código compilado se encontrará en el folder src, para iniciar el servidor ejecutar el comando:

src/redis-server

Si el servidor se inicia de forma exitosa el siguiente paso es conectarse y empezar a utilizarlo:

src/redis-cli
127.0.0.1:6379> set mykey 1000
OK
127.0.0.1:6379> get mykey
"1000"
127.0.0.1:6379>

En el ejemplo anterior se define una llave llamada mykey con un valor de 1000 y se obtiene el valor utilizando el comando get.

Accediendo a información en Redis desde Java utilizando Jedis

Para acceder a la información almacenada en Redis desde Java es necesario utilizar un cliente, existe un gran número de clientes en diferentes lenguajes, para ver la lista completa acceder a clientes redis, en este ejemplo se utilizará Jedis y es necesario incluir la siguiente dependencia en la aplicación:

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.0</version>
</dependency>

Una vez incluida la dependencia acceder a Redis solo se debe hacer lo siguiente:

import redis.clients.jedis.Jedis;

/**
 * @author raidentrance
 *
 */
public class JedisTest {
	public static void main(String[] args) {
		Jedis client = new Jedis("localhost");
		client.set("mykey", "value");
		String value = client.get("mykey");
		System.out.println(value);
		client.close();
	}
}

Estructuras de datos soportadas en Redis

Redis soporta las siguientes estructuras de datos:

  • Strings: Es el tipo de dato más básico en Redis y puede contener cualquier tipo de información y tiene un límite de 512 Megabytes.
  • Lists:  Son listas comunes ordenadas por orden de inserción.
  • Sets: Son colecciones des ordenadas y no permiten datos duplicados.
  • Hashes: Son mapas entre campos y valores, esto los hace el tipo perfecto para almacenar objetos.
  • Sorted sets: Son similares a los Sets la diferencia es que cada elemento de la colección es ordenado de acuerdo a un score.

En próximos posts se explicará como utilizar los comandos disponibles en Redis.

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

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

¿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