RSS

Añadir una libraría a nuestro repositorio maven

Aunque muchas veces lo parezca, maven no tiene todas las librerías que podamos necesitar, generalmente por problemas de licencias y demás, o simplemente, si queremos añadir a nuestro repositorio local una librería que hemos creado nosotros mismos para poder utilizarla en otros proyectos, en ambos casos, podemos instalar de forma manual dicha librería en nuestros repositorios locales.

¿Qué necesitamos para ello? Nada más fácil, la librería, un pequeño comando de consola, y saber un poco que estamos haciendo, que es lo que pretendo explicar en este artículo.

Imaginemos que como librería para el ejemplo vamos a utilizar una llamada “example-1.0.jar”, y que dicha librería es la que queremos añadir a nuestro repositorio local.

El comando a ejecutar, es tan simple como:

mvn install:install-file -Dfile=<libraryName> -DgroupId=<groupId> -DartifactId=<artifactId> -Dversion=<version> -Dpackaging=jar

Como veis es bastante simple, y cualquiera ligeramente familiarizado con maven puede reconocer que son cada uno de los parámetros, ya que son los habituales datos que rellenamos cuando añadimos las dependencias en nuestro “pom.xml”.

Con nuestra librería ejemplo, esto quedaría algo así:

mvn install:install-file -Dfile=example-1.0.jar -DgroupId=org.example -DartifactId=example -Dversion=1.0 -Dpackaging=jar

Tras esto, solo tendríamos que ir a nuestro proyecto e incluir la dependencia de la forma habitual:

<dependency>
    <groupId>org.example</groupId>
    <artifactId>example</artifactId>
    <version>1.0</version>
</dependency>

Y con esto ya estaríamos. Nos vemos.

Anuncios
 
Deja un comentario

Publicado por en 8 agosto, 2014 en prácticos, programación

 

MyBatis: Herencia de objectos

Hoy tengo uno de esos casos algo peculiares para los cuales no hay una solución buena o quizás todas sean buenas, nunca se sabe. De lo que estoy seguro es de que hay otras soluciones, y seguro que muchas de ellas mejores, sobre todo, porque yo estoy empezando a trabajar ahora con MyBatis y mis conocimientos sobre este framework no son muy extensos aún. Pero bueno, como ya sabéis el motivo del blog es aprender, y una forma de hacer esto es escribir lo que uno aprende y, posteriormente, revisar las cosas cuando uno adquiere más conocimiento.

Por ponernos en situación, el caso es este: Imaginemos que tenemos una aplicación con tres clases, las cuales extienden unas de otras, de forma que:

  • La clase A no extiende de ninguna.
  • La clase B extiende a A.
  • La clase C extiende a B.

En esta aplicación se entiende como un objeto completo a la clase C, pero a lo largo de la aplicación en determinadas ocasiones solo se requieren los datos que posee A o B, no siendo necesario tener el objeto al completo.

Además de este esquema de clases, en la BBDD, se decidió representar esta jerarquía de clases de igual modo, de forma que tenemos una tabla por cada clase y todas ellas comparten el mismo identificador.

Tras todo esto, se decidió implementar en MyBatis cumpliendo todas las restricciones y necesidades.

Antes de seguir leyendo, este artículo no es un tutorial de MyBatis (probablemente escriba uno, pero no hoy). Este artículo es solo la resolución de un caso muy concreto, otra de estas cosas que surgen y hay que solucionar sin poderse replantear el diseño de la aplicación.

Para ver como resolver esto, y repito, habrá muchas y mejores soluciones, vamos a hacer un pequeño proyecto donde:

  • La clase A será la clase “Mammal”. Con una propiedad “age” y un “id”.
  • La clase B será la clase “Human”. Con una propiedad “name” y un “id”.
  • La clase C será la clase “Gender”. Con una propiedad “gender” y un “id”.

No hagáis mucho caso a los nombre de las clases y centrados en las relaciones.

Para el ejemplo, vamos a utilizar Java y una BBDD PostgreSQL.

Lo primero, es crea un pequeño proyecto maven y añadirle las siguientes dependencias:

<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.example</groupId>
    <artifactId>MyBatis</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <name>MyBatis</name>
	
    <properties>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
	
    <dependencies>
	
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.7</version>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.3-1101-jdbc41</version>
        </dependency>
	
    </dependencies>
	
    <build>
        <finalName>mybatis</finalName>
    </build>
</project>

El siguiente paso, si no lo hemos hecho ya, es crear una BBDD para nuestro ejemplo:

drop table if exists mammal;

create table mammal (
    id integer primary key, 
    age integer
);

drop table if exists human;

create table human (
    id integer primary key, 
    name varchar(50)
);

drop table if exists gender;

create table gender (
    id integer primary key, 
    gender varchar(50)
);

Tras esto, vamos a hacer una configuración básica de MyBatis para que conecte con nuestra BBDD:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- Alias generation -->
    <typeAliases>
        <package name="org.example.model" />
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.postgresql.Driver" />
                <property name="url" value="jdbc:postgresql:earthlife" />
                <property name="username" value="postgres" />
                <property name="password" value="toor" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="org/example/model/MammalMapper.xml" />
        <mapper resource="org/example/model/HumanMapper.xml" />
        <mapper resource="org/example/model/GenderMapper.xml" />
    </mappers>

</configuration>

Y ya nos lanzamos a la implementación.

En primer lugar nuestros objectos:

package org.example.model;

public class Mammal {
    private int id;
    private int age;

    public int getId() {
        return this.id;
    }

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

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}
package org.example.model;

public class Human extends Mammal {
    private int id;
    private String name;

    @Override
    public int getId() {
        return this.id;
    }

    @Override
    public void setId(int id) {
        this.id = id;
        super.setId(id);
    }

    public String getName() {
        return this.name;
    }

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

}
package org.example.model;

public class Gender extends Human {
    private int id;
    private String gender;

    @Override
    public int getId() {
        return this.id;
    }

    @Override
    public void setId(int id) {
        this.id = id;
        super.setId(id);
    }

    public String getGender() {
        return this.gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

}

En segundo lugar, los interfaces de los Mappers:

package org.example.mappers;

import java.util.List;

import org.example.model.Mammal;

public interface MammalMapper {
    Mammal getMammalById(int id);

    List<Mammal> getAllMammals();

    int insertMammal(Mammal mammal);

    int updateMammal(Mammal mammal);

    int deleteMammal(int id);
}
package org.example.mappers;

import java.util.List;

import org.example.model.Human;

public interface HumanMapper {
    Human getHumanById(int id);

    List<Human> getAllHumans();

    int insertHuman(Human human);

    int updateHuman(Human human);

    int deleteHuman(int id);

    Human getHumanMammalById(int id);

    List<Human> getAllHumanMammals();

    int insertHumanMammal(Human human);

    int updateHumanMammal(Human human);

    int deleteHumanMammal(int id);
}
package org.example.mappers;

import java.util.List;

import org.example.model.Gender;

public interface GenderMapper {
    Gender getGenderById(int id);

    List<Gender> getAllGenders();

    int insertGender(Gender gender);

    int updateGender(Gender gender);

    int deleteGender(int id);

    Gender getGenderHumanById(int id);

    List<Gender> getAllGenderHumans();

    int insertGenderHuman(Gender gender);

    int updateGenderHuman(Gender gender);

    int deleteGenderHuman(int id);

    Gender getGenderHumanMammalById(int id);

    List<Gender> getAllGenderHumanMammals();

    int insertGenderHumanMammal(Gender gender);

    int updateGenderHumanMammal(Gender gender);

    int deleteGenderHumanMammal(int id);
}

Y, por último, los Mappers en XML:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="org.example.mappers.MammalMapper" >

    <resultMap type="Mammal" id="MammalResultMap">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="age" property="age" jdbcType="INTEGER" />
    </resultMap>
    
    <select id="getMammalById" parameterType="int" resultMap="MammalResultMap">
        select id,
               age
          from mammal
         where id = #{id}
    </select>
    
    <select id="getAllMammals" parameterType="int" resultMap="MammalResultMap">
        select id,
               age
          from mammal
    </select>
    
    <insert id="insertMammal" parameterType="Mammal">
        insert into mammal
                    (id, age)
             values (#{id, jdbcType=INTEGER}, #{age, jdbcType=INTEGER})
    </insert>
    
    <update id="updateMammal" parameterType="Mammal">
        update mammal
           set age = #{age, jdbcType=INTEGER}
         where id = #{id, jdbcType=INTEGER}
    </update>
    
    <delete id="deleteMammal" parameterType="int">
        delete from mammal
         where id = #{id, jdbcType=INTEGER}
    </delete>
    
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="org.example.mappers.HumanMapper" >

    <resultMap type="Human" id="HumanResultMap">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
    </resultMap>
    
    <resultMap type="Human" id="HumanMammalResultMap">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="age" property="age" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
    </resultMap>
    
    <!-- Human -->
    
    <select id="getHumanById" parameterType="int" resultMap="HumanResultMap">
        select id,
               name
          from human
         where id = #{id}
    </select>
    
    <select id="getAllHumans" parameterType="int" resultMap="HumanResultMap">
        select id,
               name
          from human
    </select>
    
    <insert id="insertHuman" parameterType="Human">
        insert into human
                    (id, name)
             values (#{id, jdbcType=INTEGER}, #{name, jdbcType=VARCHAR})
    </insert>
    
    <update id="updateHuman" parameterType="Human">
        update human
           set name = #{name, jdbcType=VARCHAR}
         where id = #{id, jdbcType=INTEGER}
    </update>
    
    <delete id="deleteHuman" parameterType="int">
        delete from human
         where id = #{id, jdbcType=INTEGER}
    </delete>
    
    <!-- Human + Mammal -->
    
    <select id="getHumanMammalById" parameterType="int" resultMap="HumanMammalResultMap">
        select h.id,
               m.age,
               h.name
          from human h
          left outer join mammal m on h.id = m.id
         where h.id = #{id}
    </select>
    
    <select id="getAllHumanMammals" parameterType="int" resultMap="HumanMammalResultMap">
        select h.id,
               m.age,
               h.name
          from human
          left outer join mammal m on h.id = m.id
    </select>
    
    <insert id="insertHumanMammal" parameterType="Human">
        insert into mammal
                    (id, age)
             values (#{id, jdbcType=INTEGER}, #{age, jdbcType=INTEGER});
        insert into human
                    (id, name)
             values (#{id, jdbcType=INTEGER}, #{name, jdbcType=VARCHAR})
    </insert>
    
    <update id="updateHumanMammal" parameterType="Human">
        update mammal
           set age = #{age, jdbcType=INTEGER}
         where id = #{id, jdbcType=INTEGER};
        update human
           set name = #{name, jdbcType=VARCHAR}
         where id = #{id, jdbcType=INTEGER}
    </update>
    
    <delete id="deleteHumanMammal" parameterType="int">
        delete from human
         where id = #{id, jdbcType=INTEGER};
        delete from mammal
         where id = #{id, jdbcType=INTEGER}
    </delete>
    
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="org.example.mappers.GenderMapper" >

    <resultMap type="Gender" id="GenderResultMap">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="gender" property="gender" jdbcType="VARCHAR" />
    </resultMap>
    
    <resultMap type="Gender" id="GenderHumanResultMap">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <result column="gender" property="gender" jdbcType="VARCHAR" />
    </resultMap>
    
    <resultMap type="Gender" id="GenderHumanMammalResultMap">
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="age" property="age" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <result column="gender" property="gender" jdbcType="VARCHAR" />
    </resultMap>
    
    <!-- Gender -->
    
    <select id="getGenderById" parameterType="int" resultMap="GenderResultMap">
        select id,
               gender
          from gender
         where id = #{id}
    </select>
    
    <select id="getAllGenders" parameterType="int" resultMap="GenderResultMap">
        select id,
               gender
          from gender
    </select>
    
    <insert id="insertGender" parameterType="Human">
        insert into gender
                    (id, gender)
             values (#{id, jdbcType=INTEGER}, #{gender, jdbcType=VARCHAR})
    </insert>
    
    <update id="updateGender" parameterType="Human">
        update gender
           set gender = #{gender, jdbcType=VARCHAR}
         where id = #{id, jdbcType=INTEGER}
    </update>
    
    <delete id="deleteGender" parameterType="int">
        delete from gender
         where id = #{id, jdbcType=INTEGER}
    </delete>
    
    <!-- Gender + Human -->
    
    <select id="getGenderHumanById" parameterType="int" resultMap="GenderHumanResultMap">
        select g.id,
               h.name,
               g.gender
          from gender g
          left outer join human h on g.id = h.id
         where g.id = #{id}
    </select>
    
    <select id="getAllGenderHumans" parameterType="int" resultMap="GenderHumanResultMap">
        select g.id,
               h.name,
               g.gender
          from gender g
          left outer join human h on g.id = h.id
    </select>
    
    <insert id="insertGenderHuman" parameterType="Gender">
        insert into human
                    (id, name)
             values (#{id, jdbcType=INTEGER}, #{name, jdbcType=VARCHAR});
        insert into gender
                    (id, gender)
             values (#{id, jdbcType=INTEGER}, #{gender, jdbcType=VARCHAR})
    </insert>
    
    <update id="updateGenderHuman" parameterType="Gender">
        update human
           set name = #{name, jdbcType=VARCHAR}
         where id = #{id, jdbcType=INTEGER};
        update gender
           set gender = #{gender, jdbcType=VARCHAR}
         where id = #{id, jdbcType=INTEGER}
    </update>
    
    <delete id="deleteGenderHuman" parameterType="int">
        delete from gender
         where id = #{id, jdbcType=INTEGER};
        delete from human
         where id = #{id, jdbcType=INTEGER}
    </delete>
    
    <!-- Gender + Human + Mammal -->
    
    <select id="getGenderHumanMammalById" parameterType="int" resultMap="GenderHumanMammalResultMap">
        select g.id,
               m.age,
               h.name,
               g.gender
          from gender g
          left outer join human h on g.id = h.id
          left outer join mammal m on g.id = m.id
         where g.id = #{id}
    </select>
    
    <select id="getAllGenderHumanMammals" parameterType="int" resultMap="GenderHumanMammalResultMap">
        select g.id,
               m.age,
               h.name,
               g.gender
          from gender g
          left outer join human h on g.id = h.id
          left outer join mammal m on g.id = m.id
    </select>
    
    <insert id="insertGenderHumanMammal" parameterType="Gender">
        insert into mammal
                    (id, age)
             values (#{id, jdbcType=INTEGER}, #{age, jdbcType=INTEGER});
        insert into human
                    (id, name)
             values (#{id, jdbcType=INTEGER}, #{name, jdbcType=VARCHAR});
        insert into gender
                    (id, gender)
             values (#{id, jdbcType=INTEGER}, #{gender, jdbcType=VARCHAR});
    </insert>
    
    <update id="updateGenderHumanMammal" parameterType="Gender">
        update mammal
           set age = #{age, jdbcType=INTEGER}
         where id = #{id, jdbcType=INTEGER};
        update human
           set name = #{name, jdbcType=VARCHAR}
         where id = #{id, jdbcType=INTEGER};
        update gender
           set gender = #{gender, jdbcType=VARCHAR}
         where id = #{id, jdbcType=INTEGER}
    </update>
    
    <delete id="deleteGenderHumanMammal" parameterType="int">
        delete from gender
         where id = #{id, jdbcType=INTEGER};
        delete from human
         where id = #{id, jdbcType=INTEGER};
        delete from mammal
         where id = #{id, jdbcType=INTEGER}
    </delete>
    
</mapper>

Como podéis ver, todo está preparado para poder trabajar con los tres objetos a lo largo de todas la aplicación, y que para aquellos que heredan se pueda obtener el objeto entero con todas sus propiedades, o solo partes de dicho objeto.

La única cosa digna de mención, ya que el código es fácilmente entendible, es la utilización del objeto “super” en los constructores, para poder hacer una asignación encadenada de dicho “id” ya que este es compartido por los tres objetos en las tres tablas. Si no lo añadiéramos, tendríamos en muchas ocasiones los ids de los objetos padres a null.

Aunque otra cosa que debería de nombrar es que el código no es independiente de la BBDD que utilicemos. Quiero decir, PostgreSQL soporte la ejecución consecutiva de varias instrucciones tal y como están en los Mappers, pero si utilizáramos una BBDD Oracle, por ejemplo, tendríamos que encerrar esas sentencias consecutivas entre un “begin” y un “end;”:

begin
    select ...;
    select ...;
    ...
end;

Estoy casi seguro de que se puede acortar mucho más el código trabajando con herencia de los objetos resultado en MyBatis o quizás reutilizando las sentencias SQL de unos mappers a otros, pero de momento, eso, está aún por investigar.

Nos vemos.

 
Deja un comentario

Publicado por en 3 agosto, 2014 en programación

 

El límite está en tu mente no en el lenguaje

Se que por el título del artículo, más de uno pensará que me voy a poner a filosofar sobre unas cosas u otras, pero nada dista más de mi intención. Lo que pasa es que al igual que no se porque he escrito la porción de código que os traigo hoy, no sabía que título ponerle.

Hoy es uno de esos días donde en una discusión sobre diseño, salta alguien diciendo si tal cosa o tal otra se puede o no se puede hacer, y si Java no lo soporta y ese tipo de discusiones que no llegan a ningún lado. En concreto, era el paso como parámetro de una función (véase que estamos por razones de proyecto usando la versión 1.7 y no la 1.8). Es cierto, que en este punto no te queda más remedio que concederle la razón, sobre todo si entra en comparaciones con JavaScript (salvando las distancias). Pero, he aquí que a uno después de estas discusiones, se le queda un no sé qué que que se yo que tiene que ponerse a escribir algo de código a ver que puede hacer.

La idea, era escribir una operación de CRUD que en ciertas ocasiones necesita crear una transacción y en otras no. Lo más común es duplicar los métodos, en uno la lógica y en otro el manejo de la transacción y la llamada al que tiene la lógica (seguro que habrán métodos mejores, peores y de todo tipo, pero no es el punto de este artículo).

Pues bien, para hacer una aproximación de como pasar funciones como parámetros (sin que el lenguaje lo permita) yo me he decantado por una clase abstracta. A continuación, os pongo el código y luego, lo explicaré un poco, aunque es muy básico.

package com.wordpress.infow.chorradas;

import org.apache.log4j.Logger;

import com.wordpress.infow.db.TransactionManager;

public abstract class GenericExec {

    static Logger logger = Logger.getLogger(GenericExec.class);

    public static final int CREATE = 0;
    public static final int READ = 1;
    public static final int UPDATE = 2;
    public static final int DELETE = 3;

    abstract public String create(Object obj, TransactionManager tr);

    abstract public Object read(Object obj, TransactionManager tr);

    abstract public void update(Object obj, TransactionManager tr);

    abstract public void delete(Object obj, TransactionManager tr);

    public Object withTransaction(GenericExec generic, Object data, int operation) {
        Object obj = null;

        TransactionManager tr = new TransactionManager();

        try {
            tr.initTransaction();

            switch (operation) {
                case CREATE:
                    obj = generic.create(data, tr);
                    break;

                case READ:
                    obj = generic.read(data, tr);
                    break;

                case UPDATE:
                    obj = generic.update(data, tr);
                    break;

                case DELETE:
                    obj = generic.delete(data, tr);
                    break;
            }

            tr.commit();
        } catch (Exception e) {
            GenericExec.logger.error(generic.getClass() + "::" + operation + " " + e.getMessage());
        } finally {
            tr.closeTransaction();
        }

        return obj;
    }
}

Como se puede ver, la clase abstracta obliga a implementar los métodos que deseamos, las operaciones de CRUD, y posee la implementación de la función que nos va a permitir ejecutar estas con una transacción alrededor. Todo muy básico. Además, de esta clase van a extender muchas otras que va a recibir parámetros de tipo diferente y devolver parámetros de tipo diferente para cada una de las clases que heredan.

Yo creo que no necesita más explicación. Tiene algunas pegas, como que hay que tener la precaución de realizar los casting apropiados desde el objeto Object al que deseamos utilizar, pero por lo demás es bastante manejable.

De nuevo, recordados que esto solo es un juguete, una chorrada implementada a partir de un debate sobre algo, y sin más sentido que como reto o idea, y ver que aunque muchas veces un lenguaje no nos provee de algo, siempre podemos implementarnos algo, como reza el título del artículo, los límites muchas veces están en la mente del desarrollado no en el lenguaje. Y no hablo de inteligencia, sino de ganas de jugar un rato con el lenguaje o falta de tiempo, o cosas de este estilo.

Bueno, espero que como curiosidad os haya gustado. Si alguien tiene alguna duda, preguntad sin miedo. Nos vemos.

 
Deja un comentario

Publicado por en 31 julio, 2014 en programación

 

ElasticSearch: Demo

Debido a recientes cambios en el ámbito laboral, recientemente he empezado a trabajar con ElasticSearch y me ha tocado, cosa genial no os equivoquéis, estudiar un poco esta tecnología, como funciona y, antes de empezar con el proyecto real, realizar unas pequeñas demos. Una de las primeras, es la que os traigo aquí. Es bastante simple, pero para explicar los primeros pasos es bastante aceptable.

Para empezar, ¿qué es ElasticSearch? Básicamente nos ofrece una instancia de un servidor para la indexación y búsqueda de contenido añadido a la instancia. Además, nos ofrece un API REST para poder acceder a la información.

El primer paso, sería instalar una instancia en nuestra máquina local. Nada más fácil, solo hay que descargar un pequeño .zip y descomprimirlo. Tenéis el ejecutable y las instrucciones en la página aquí.

Como siguiente paso, es bastante interesante, en entornos de desarrollo, ya que sino requiere el pago de una licencia, es instalar Marvel, que además de muchos paneles con información varia, tiene un panel muy interesante llamado “Sense” en el que podemos escribir nuestras consultas y consumir directamente el API REST de ElasticSearch y además, nos ofrece cierto grado de auto completado.

Para instalarlo, nada más fácil, nos dirigimos al directorio de instalación de ElasticSearch y ejecutamos:

bin/plugin -i elasticsearch/marvel/latest

Tras esto, podremos acceder a los paneles con la URL:

http://any-server-in-cluster:9200/_plugin/marvel/

Aquí podemos seleccionar el panel de Sense y jugar un rato con las queries que nos ofrece la documentación y el API REST que nos ofrece ElasticSearch. Pero bueno, el objetivo de este post es usar Java para ejecutar operaciones sobre la instancia de ElasticSearch.

El projecto lo tenéis en la URL habitual del repositorio aquí.

El projecto consta de unos pocos ficheros, ya que lo he hecho lo más básico posible y intentando añadir solo el código necesario para para ejecutar las operaciones de añadir (index), actualizar (update), borrar (delete) y ejecutar las búsquedas.

El fichero pom.xml contiene unas pocas dependencias básicas, todas ellas fácilmente identificables:

<!-- ElasticSearch -->
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>1.1.0</version>
</dependency>

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-json</artifactId>
    <version>1.18.1</version>
</dependency>

<!-- Apache Common BeanUtils -->
<dependency></span></div>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.2</version>
</dependency>

Tras echarle una ojeada a esto que yo creo que no necesita ninguna explicación, la otra clase interesante es la de ElasticSearch.

public static final String INDEX_NAME = "myindex"; // Must be lowercase
public static final String TYPE_NAME = "article";
public static final String SERVER = "localhost";
public static final int PORT= 9300;

Las variable que utilizaremos para hacer las conexiones a la instancia de ElasticSearch. Tenemos el nombre del índice y el tipo de objeto que vamos a insertar. Además, de ellos tenemos los datos de conexión al la instancia.

Por da una introducción breve y equiparar este sistema al que todos conocemos de BBDD relacionales, el equivalente, sería algo así:

Relacionales: Esquema – Tabla – Columna – Campo

ElasticSearch: Índice – Tipo – Objecto – Campo

De esta forma yo creo que es fácil de entender. Si luego necesitáis crear clusters o distribuir vuestros índice en varias instancias ya tendréis que tomar decisiones entre que es mejor, si un objeto por índice, o un índice con muchos tipos dentro, o…

Volviendo a la clase Java, si nos fijamos básicamente, todos los métodos en ella tiene la misma estructura: La conexión con el objeto “Client”, y la operación que queremos realizar pasándole los datos que necesitamos.

Client client = new TransportClient().addTransportAddress(new InetSocketTransportAddress(ElasticSearch.SERVER, ElasticSearch.PORT));

El único método que destaca un poco, es el método update que en vez de realizar una operación de actualización como tal, ejecuta un borrado y una nueva inserción, esto es así porque la velocidad de trabajo de ElasticSearch lo permite y además, el API no ofrece la posibilidad de actualización. Bueno, realmente, sí que se puede hacer a través de una ejecución de scripts, pero no merece la pena, al menos, en un caso tan general como este.

// Update the element in ElasticSearch
public static void updateArticle(Article article) throws Exception {
    ElasticSearch.deleteArticle(article.getId());

    ElasticSearch.indexArticle(article);
}

En el caso de la búsqueda, en el ejemplo, se ha utilizado una de las “queries” más generales que hay, y sobre esto sí que tenéis que echarle un ojo más a fondo porque hay muchísimas de ellas, con múltiples opciones. Pero para este ejemplo, era más que suficiente con esta búsqueda básica.

QueryBuilder queryBuilder = QueryBuilders.matchQuery("_all", searchTerm);

Echadle un ojo al código, y si tenéis alguna duda ya sabéis. Nos vemos.

 
Deja un comentario

Publicado por en 26 julio, 2014 en ElasticSearch

 

Planificar tareas en Java

En algunas ocasiones nos vemos en la necesidad de que nuestras aplicaciones lancen alguna tarea en segundo plano para llevar a cabo algún procedimiento, generar algún informe, recolectar nuevas alertas, … este tipo de cosas. En este caso lo mejor es lanzarlas en segundo plano y no en el hilo principal. Para esto, Java nos ofrece tres posibilidades diferentes que vamos a ver a continuación a modo de chuleta de fácil y rápido acceso.

La primera de las formas es usando un simple hilo. Crearíamos el hilo con la tareas, lo lanzaríamos indefinidamente y le asignaríamos un intervalo de espera. Esto quedaría algo así:

public class Example1 {
    public static void main(String[] args) {
        final long interval = 2000; // Each 2 seconds
        Runnable runnable = new Runnable() {
            public void run() {
                while (true) {
                    System.out.println("I am here!!!");
                    try {
                        Thread.sleep(interval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
    }
}

Como podéis ver es bastante simple. Basta con crear el objeto Runnable, crear el comportamiento que deseamos y lanzarlo. No tenemos mucho control sobre cuando empezar o parar el hilo y el proceso se lanzará de forma inmediata, pero el objetivo era ser simple.

El siguiente ejemplo que vamos a ver cubre las necesidades que no nos ofrece la solución anterior, que en definitiva son, tener un poco más de control sobre muestro proceso. Para ello vamos a utilizar las clases Timer y TimerTask. Además, la instancia de Timer es thread-safe.

public class Example2 {
    public static void main(Strings[] args) {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("I am here!!!");
            }
        };

        Timer timer = new Timer();
        long delay = 0;
        long interval = 2000;

        timer.scheduleAtFixedRate(task, delay, interval);
    }
}

Como se puede ver en este ejemplo, aunque un poquito más complejo, nos ofrece la posibilidad de definir más parámetros para nuestra tarea.

El tercer y último método que vamos a ver, nos permite aún más flexibilidad de la que ya teníamos en el caso anterior. Las clases que nos permiten la implementación de esta última fueron añadidas en Java SE 5.

public class Example3 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            public void run() {
                system.out.println("I am here!!!");
            }
        };

        ScheduledExecutorService ses = Executors.
                newSingleThreadScheduledExecutor(runnable, 0, 1, TimeUnit.SECONDS);
    }
}

Este como vemos es en el que menos código escribimos, el más configurable a la hora de lanzar nuestro proceso y, además, aunque aquí no lo hemos usado, nos ofrece la posibilidad de tener un Pool de hilos para las ejecuciones.

Bueno, hasta aquí todo por hoy. Nos vemos.

Nota: Basado en el post How to Schedule a task to run in an interval.

 
2 comentarios

Publicado por en 14 abril, 2014 en programación

 

JavaEE: QuoteWall

La primera aplicación publicada en el nuevo repositorio mencionado anteriormente, se trata de una simple aplicación implementada en Java EE 7. En ella se puede observar el uso de varias tecnologías como:

  • JPA (MySQL)
  • JAX-RS
  • JAX-WS
  • EJB
  • JSF (PrimeFaces)
  • I18N y L10N
  • Bootstrap
  • Maven y Git

Toda ellas ha sido desplegada en un servidor GlassFish durante su desarrollo, con un DataSource configurado en él.

Como podréis ver echándole un rápido vistazo al código, es una aplicación muy simple que consiste únicamente en un muro de citas famosas dichas por personajes famosos o anónimos. No lleva ningún tipo de seguridad, ni gestión de usuarios ni nada por el estilo. El objetivo  de la aplicación no era más que usar algunas de las tecnologías de Java EE 7 y practicar un poco con Bootstrap, que siendo un desarrollado de Back-end, nuca está de más.

Para el que no lo sepa, Bootstrap es un Framework para front-end muy popular a día de hoy, y que nos hace muy fácil maquetar nuestras páginas y maquetarlas en diferentes dispositivos,

La aplicación la podéis encontrar en el repositorio de ejemplos java ee bajo el nombre de QuoteWall.

La aplicación tiene el típico esquema de directorios que nos aporta el usar maven, aunque en este caso, no ha sido creada a partir de un arquetipo, sino que se ha generado un fichero pom.xml vacío, y se le ha añadido el contenido necesario, incluyendo las dependencias necesarias para correr nuestra aplicación.

Como ya he comentado para las conexiones con base de datos se ha usado un servidor MySQL, y se ha creado en el servidor de aplicaciones GlassFish un DataSource llamado “jdbc/quoteWall”. Por lo demás, es un proyecto bastante estándar que podéis tomar como referencia para la implementación y creación de otros más grandes.

Como plus, se ha añadido otro proyecto más al repositorio: QuoteWallNoSQL, el cual es exactamente el mismo proyecto, pero con los cambios necesarios para hacerlo funcionar con una base de datos NoSQL, en este caso, MongoDB, de lo cual ya escribimos una par de artículos anteriormente aquí en el blog. Si no los habéis visto, os remito a la serie de artículos de NoSQL.

Bueno, hasta aquí todo. Nos vemos.

 
Deja un comentario

Publicado por en 30 marzo, 2014 en JavaEE

 

Vientos de cambios

Dicen que si uno no sigue en movimiento, al final, todo lo que tiene alrededor lo acaba engullendo y, posteriormente escupiendo. Así que este es uno de esos momento es los que hay que cambiar algo para seguir avanzando.

En primer lugar, he cerrado la cuenta habitual de Twitter: @sv0b0da y he abierto una nueva: @fjavierm_ que será la que use a partir de ahora para publicar los artículos cuando salgan, retuitear cosas interesantes que vea y todo eso que solemos hacer la mayoría de nosotros en twitter.

En segundo lugar, he abierto un nuevo repositorio en github, lo podéis encontrar en: https://github.com/fjavierm. El antiguo repositorio que usaba para los ejemplos del blog seguirá existiendo, y cuando escriba código para el blog se seguirá subiendo a ese repositorio: https://bitbucket.org/svoboda. El motivo de la apertura del nuevo repositorio es que en este se van a ir subiendo aplicaciones más estructuradas, ejemplos más serios de código que los alojados en el repositorio alojado en bitbucket. Con esto no quiere decir que el código subido en el repositorio alojado en bitbucked no haga lo que tiene que hacer, o no demuestre lo que tiene que demostrar, simplemente que el código en dicho repositorio, se escribe en un formato que hace que copiarlo en los diferentes artículos sea fácil y rápido, pero no sigue, por lo general, ninguna pauta de patrones, estilo, buenas práctica o similares. Es código para ayudar a entender una idea o un concepto y que se adapta fácilmente al formato de artículos que sigue un blog por línea general.

Hasta aquí, los cambios perceptibles. Si todo va bien, no vais a notar ningún cambio más. He estado ponderando el cambiar también el nombre de usuario de este blog “svoboda”, pero la verdad es que todo sea dicho, me ha acompañado en muchas, que digo muchas, muchísimas cosas y le tengo mucho cariño, así que como un guiño a los tiempos que han pasado, he decidido respetarlo y mantenerlo.

Como podéis ver todo ha tomado un carácter un poco más serio, dejo de ser alguien siempre tras un nick para transformarme un poco más en alguien tras un nombre, una especie de marca, y espero trabajar para mejorarla y hacerla más grande día a día.

Por último, si alguno está pensando que el tono del blog va a cambiar, puede estar tranquilo, ya que va a seguir manteniendo la misma temática y el mismo modo de apuntes personales abiertos a todo el mundo, y como siempre, yo voy a seguir disponible para vuestras consultar.

Espero que todos estos cambios sean para bien, y que todos podáis percibirlos. Nos vemos.

 
Deja un comentario

Publicado por en 30 marzo, 2014 en anuncios