sábado, 7 de noviembre de 2015

Proyectos Gradle en Eclipse usando Buildship

Introducción

En posts anteriores se ha venido hablando de Gradle y desarrollando algunos ejemplos usando comandos (Terminal/Consola). Dado que en el desarrollo de aplicaciones con Java lo más común es apoyarse en un IDE, en este post se mostrará cómo usar el nuevo plugin para Eclipse desarrollado por los mismos creadores de Gradle: Buildship.

Instalación

La manera más fácil y recomendada de instalar Buildship en Eclipse es mediante "Eclipse Marketplace" (menú Help -> Eclipse Marketplace).

Figura 1 - Buildship en Eclipse Marketplace

Creación de un Nuevo Proyecto

Luego de seguir los pasos del proceso de instalación y reiniciar Eclipse ya será posible crear nuevos proyectos usando Gradle:

Figura 2 - Nuevo proyecto Gradle

El proceso de creación del proyecto permitirá indicar si se utilizará el wrapper de Gradle o alguna instalación específica. Como se indicó en un post previo, es recomendable usar el wrapper.

Figura 3 - Seleccionar versión de Gradle

Al completar los pasos para la creación del proyecto se tendrá la siguiente estructura de archivos:

Figura 4 - Proyecto Gradle creado en Eclipse

Como puede verse en la imagen anterior, el archivo "build.gradle" se está mostrando en un editor de texto sencillo, sin sintaxis coloreada (Syntax highlighting). Esto es algo que ya tienen presente los desarrolladores de Buildship y se encuentra documentado en sus historias de usuario, junto con otras funcionalidades, aunque aún no se define un marco de tiempo para su desarrollo.

Entre tanto lo que puede hacerse es instalar adicionalmente el plugin "Minimalist Gradle Editor":

Figura 5 - Instalando Minimalist Gradle Editor

Una vez instalado aparecerá una nueva opción en el menú contextual "Open With" llamada "Minimalist Gradle Editor":

Figura 6 - Abrir con Minimalist Gradle Editor

Figura 7 - Archivo abierto con Minimalist Gradle Editor

JCenter

Como se puede ver en el archivo "build.gradle", en la sección de repositorios (repositories) no aparece "mavenCentral()" como quizás algunos estaban acostumbrados, sino que en su lugar ahora está "jcenter()". En resumen, JCenter es un repositorio Maven el cual no solamente ya contiene las librerías en Maven Central sino también las de otros repositorios de librerías que, generalmente por decisión de sus desarrolladores, se conservan en repositorios aparte.

Adicional a esto, JCenter cuenta con un búsqueda optimizada de librerías (lo cual consume menos memoria al buscar dependencias en los proyectos), conexión HTTPS (para brindar mejor seguridad), entre otras características.

Este repositorio ganó mucha popularidad cuando Google decidió que este sería el repositorio por defecto para el plugin Gradle de Android en Android Studio. Más información puede verse en: http://www.technotalkative.com/android-studio-migration-maven-central-jcenter/

Ejecutando Tareas de Gradle

Vistas "Gradle Tasks" y "Gradle Executions"

Buildship adiciona a Eclipse las vistas "Gradle Tasks" y "Gradle Executions". En la primera aparece el listado de las tareas disponibles para ejecutar en el proyecto, igual al resultado de ejecutar la tarea "tasks".

Figura 8 - Vista Gradle Tasks

Al hacer doble clic en alguna de las tareas, o seleccionarla mediante clic derecho "Run Gradle Task", se ejecutará la tarea y su resultado se mostrará en las vistas "Gradle Executions" y "Console". Como valor agregado de "Gradle Executions" se tiene la opción de cancelar la ejecución de una tarea en proceso y ver el resultado de ejecuciones previas mediante los botones de su barra superior.

Nota: "Gradle Executions" sólo funciona cuando el proyecto usa Gradle 2.4 ó superior.

Figura 9 - Tarea ejecutada en Gradle Executions

Figura 10 - Tarea ejecutada en Console

Run Configurations

También es posible ejecutar tareas desde esta opción, haciendo clic derecho sobre el proyecto y seleccionando en el menú contextual "Run As" -> "Run Configurations".

Figura 11 - Ventana Run Configurations con Gradle

En el campo "Gradle Tasks" de la pestaña "Gradle Tasks" (sí, se llaman igual) se indican las tareas que se quieren ejecutar y opcionalmente en la pestaña "Arguments" se pueden ingresar los parámetros que dichas tareas puedan necesitar.

Una vez ejecutada la tarea desde "Run Configurations" esta queda disponible desde la opción "Run" de Eclipse para mayor comodidad.

Figura 12 - Tarea de Gradle desde Run

Actualizar el Proyecto Gradle

Luego de modificar alguno de los archivos de Gradle en el proyecto (por ejemplo agregar una dependencia en "build.gradle"), se debe actualizar la configuración de Gradle haciendo clic derecho sobre el proyecto y seleccionar "Gradle" -> "Refresh Gradle Project".

Figura 13 - Refrescar proyecto Gradle

Figura 14 - Nueva dependencia en el proyecto

Importar un Proyecto Gradle

Mediante Buildship no solamente es posible crear proyectos Gradle nuevos, sino también importar los que se hayan desarrollado previamente mediante la opción "File" -> "Import" -> "Gradle" -> "Gradle Project":

Figura 15 - Importando un proyecto Gradle

Al finalizar la importación, el proyecto queda disponible en el workspace de Eclipse y, usando como ejemplo el proyecto con Retrofit desarrollado en otro post, puede verse que las tareas de los plugins "release" y "application" aparecen en "Gradle Tasks", junto con todas las demás.

Figura 16 - Proyecto Gradle importado

Depurar una aplicación Java (Debug)

Las opciones "Run" y "Debug" de Eclipse funcionan normalmente al aplicarlas sobre una clase que tenga un método "main", sin embargo en algunas ocasiones se tienen tareas de Gradle que se requieren ejecutar para ejecutar el proyecto, por ejemplo copia de archivos, reemplazo de variables, etc. Infortunadamente las opciones "Run" y "Debug" de Eclipse simplemente ejecutan la aplicación sin tener en cuenta dichas tareas de Gradle.

Desde la vista "Gradle Tasks" se puede ejecutar la tarea "run" para los proyectos que usen el plugin "application", como es el caso del proyecto "retrofit-example" que se importó previamente. De esta manera es posible ejecutar la aplicación teniendo en cuenta todas las tareas de Gradle necesarias, sin embargo aún no permite ejecutarla en modo debug.

Para ejecutar una tarea de Gradle en modo debug debe agregarse el parámetro "--debug-jvm", lo cual puede hacerse cuando se ejecuta desde la consola/terminal o desde la opción "Run Configurations" de Eclipse.

Figura 17 - Tarea Gradle en modo Debug desde Eclipse

Al ejecutar la tarea con ese parámetro se compila el proyecto pero la ejecución se detiene, esperando que un proceso de debug se conecte al puerto 5005.

:compileJava
:processResources
:classes
:run
Listening for transport dt_socket at address: 5005

En Eclipse se tiene la opción de iniciar un proceso de debug que se conecte al puerto que se necesite. Para ello se debe seleccionar "Debug Configurations" -> "Remote Java Application" desde la opción "Debug".

Figura 18 - Debug Configurations señalando la opción Debug

Figura 19 - Ventana Debug Configurations

Figura 20 - Aplicación en punto de interrupción (breakpoint) en Eclipse

Nota: En caso de que al llegar al breakpoint en Eclipse no se muestre el código de la clase, se debe terminar esa ejecución, iniciar una vez la aplicación usando la opción "Debug" de Eclipse y al intentar de nuevo usando la tarea de Gradle y el debug remoto de Eclipse ya debe aparecer el código.

Conclusiones

Buildship aún carece de varias características que lo hagan una integración ideal entre Eclipse y Gradle, quienes usen Android Studio (o IntelliJ IDEA) ya disfrutan no solamente de sintaxis resaltada sino también validaciones y auto-completado.

De hecho, los desarrolladores de Spring también hicieron un plugin de Gradle para Eclipse (Gradle Integration for Eclipse), el cual se lanzó antes que Buildship, así que si a eso se suma el que la empresa detrás de Gradle ahora haga parte de Eclipse Foundation y tenga un plugin oficial desarrollado por ellos mismos dan una buena señal para quienes aún usamos Eclipse.

Referencias

https://gradle.org/eclipse
https://eclipse.org
https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html
http://www.ibm.com/developerworks/library/os-eclipse-javadebug

Más sobre Gradle

http://nombre-temp.blogspot.com/2016/01/tutorial-gradle.html

martes, 28 de abril de 2015

Proyectos Gradle con múltiples módulos

Introducción

Al igual que Maven, Gradle tiene soporte para proyectos con múltiples módulos (multi-modulemulti-project). Esto permite tener un conjunto de configuraciones y librerías comunes, compartidas entre varios proyectos relacionados. Esto puede usarse tanto en aplicaciones Java como en librerías de clases o aplicaciones Web, inclusive en proyectos que no son Java.

Para demostrar esta funcionalidad se desarrollarán unos proyectos muy simples:
  • common: tendrá la clase "Util" con un método que imprimirá en consola una cadena indicada como parámetro, intercambiando las mayúsculas y las minúsculas.
  • hello-world: tendrá la clase "Example" con un método "main" usando la clase "Util" para imprimir el texto "Hello World".
  • hola-mundo: tendrá la clase "Example" con un método "main" usando la clase "Util" para imprimir el texto "Hola Mundo". 
En este escenario el proyecto "common" será una librería de clases Java, la cual será usada por los proyectos "hello-world" y "hola-mundo" para imprimir textos de manera "estandarizada".

El proyecto completo se podrá descargar desde: https://github.com/guillermo-varela/gradle-multi-project-example

Proyecto Raíz/Padre (Root/Parent)

Este es el proyecto que contiene a los demás sub-proyectos (o módulos). Para empezar se creará la siguiente estructura de carpetas y archivos:
gradle-multi-project-example
..build.gradle
..gradle.properties
..settings.gradle
..common/
..hello-world/
..hola-mundo/

Las carpetas "common", "hello-world" y "hola-mundo" corresponden a los sub-proyectos, mientras que los demás archivos tendrán la configuración de Gradle común a todos estos.

Archivo build.gradle

plugins {
  id 'net.researchgate.release' version '2.0.2'
}

subprojects {
    apply plugin: 'java'

    project(':hello-world') {
        apply plugin: 'application'
    }

    project(':hola-mundo') {
        apply plugin: 'application'
    }

    sourceCompatibility = 1.7
    targetCompatibility = 1.7

    repositories {
        mavenCentral()
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

Al principio se indica que todo el proyecto usará el plugin release para automatizar el versionamiento, como se indicó en un post anterior.

Luego se tiene la sección "subprojects" en la cual se indican las configuraciones y tareas que se aplicarán a los sub-proyectos. En este caso se indica que todos serán proyectos Java, compilando con el JDK 1.7 y usarán el repositorio Maven Central. También se indica que solamente los proyectos "hello-world" y "hola-mundo" serán aplicaciones ejecutables. Esto es preferible que se encuentre dentro de la configuración de Gradle de cada sub-proyecto, pero se dejó así para demostrar cómo aplicar configuraciones diferenciadas según el proyecto.

Ya al final del archivo se indica que se usará el Wrapper de Gradle en su versión 2.3.

Arhivo gradle.properties

version=1.0.0-SNAPSHOT
Nota: es importante notar que todos los sub-proyectos usarán la misma versión que se indica en el proyecto raíz, dado que de momento el plugin release no soporta tener diferentes números de versión: https://github.com/researchgate/gradle-release#multi-project-builds

Archivo settings.gradle

include ":hello-world", ":hola-mundo", ":common"

En este archivo se indican cuales son los sub-proyectos a tener en cuenta.

Proyecto common

Cuando se usan múltiples proyectos con Gradle no es obligatorio tener un archivo de configuración en todos los proyectos, si toda la configuración necesaria está en el proyecto raíz. Sin embargo en este caso se tendrá la identificación del proyecto en el JAR generado y una dependencia.

Archivo build.gradle

jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Multi-Project Example - Common', 'Implementation-Version': version
    }
}

dependencies {
    compile 'org.apache.commons:commons-lang3:3.3.2'
}

Archivo Util.java

package com.blogspot.nombre_temp.gradle.multi_project.common;

import org.apache.commons.lang3.StringUtils;

public class Util {
  public static void println(String text) {
    System.out.println(StringUtils.swapCase(text));
  }
}

Esta clase (ubicada en la ruta "gradle-multi-project-example/common/src/main/java/com/blogspot/nombre_temp/gradle/multi_project/common/Util.java") es la que usarán los otros proyectos para imprimir cadenas.

Proyecto hello-world

Archivo build.gradle

mainClassName = 'com.blogspot.nombre_temp.gradle.multi_project.hello_world.Example'

jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Multi-Project Example - Hello World', 'Implementation-Version': version
        attributes 'Main-Class': mainClassName
    }
}

dependencies {
    compile project(':common')
}

Dado que buena parte de la configuración de Gradle está en el proyecto raíz, aquí solamente se indica cual será la clase con el método "main" (punto de entrada de la aplicación) y se indica que se tendrá como dependencia el proyecto "common". Nótese que para declarar la dependencia con ese proyecto se usó "compile project(':common')" en lugar de una nombre completo de un repositorio Maven como en otros casos. Adicionalmente no fue necesario incluir manualmente la dependencia de Apache Commons Lang, ya que esta se trae por trasitividad al tener la dependencia con "common".

Nota: de ser posible la única relación entre los sub-proyectos en las configuración de Gradle debe ser a nivel de dependencias, no de configuraciones (por ejemplo que un proyecto no use o modifique tareas de Gradle de otro proyecto), esto con el fin de mantener los proyectos lo menos acoplados posible.

Archivo Example.java

package com.blogspot.nombre_temp.gradle.multi_project.hello_world;

import com.blogspot.nombre_temp.gradle.multi_project.common.Util;

public class Example {
  public static void main(String[] args) {
    Util.println("Hello World");
  }
}

Como se explicaba inicialmente, esta clase (ubicada en la ruta "gradle-multi-project-example/hello-world/src/main/java/com/blogspot/nombre_temp/gradle/multi_project/hello_world/Example.java") contiene el método "main" y usa la clase "Util" para imprimir la cadena "Hello World".

Proyecto hola-mundo

Es casi idéntico al proyecto "hello-world", pero imprimiendo "Hola Mundo".

Archivo build.gradle

mainClassName = 'com.blogspot.nombre_temp.gradle.multi_project.hola_mundo.Example'

jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Multi-Project Example - Hola Mundo', 'Implementation-Version': version
        attributes 'Main-Class': mainClassName
    }
}

dependencies {
    compile project(':common')
}

Archivo Example.java

package com.blogspot.nombre_temp.gradle.multi_project.hola_mundo;

import com.blogspot.nombre_temp.gradle.multi_project.common.Util;

public class Example {
  public static void main(String[] args) {
    Util.println("Hola Mundo");
  }
}

Construcción y Ejecución

Para construir todos los proyectos, basta con abrir una instancia de Terminal (línea de comandos) y ejecutar el comando "gradle wrapper" para inicializar el Wrapper de Gradle y luego "./gradlew build" (o "gradlew.bat build" si se está usando Windows).
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:common:assemble
:common:compileTestJava UP-TO-DATE
:common:processTestResources UP-TO-DATE
:common:testClasses UP-TO-DATE
:common:test UP-TO-DATE
:common:check UP-TO-DATE
:common:build

:hello-world:compileJava
:hello-world:processResources UP-TO-DATE
:hello-world:classes
:hello-world:jar
:hello-world:startScripts
:hello-world:distTar
:hello-world:distZip
:hello-world:assemble
:hello-world:compileTestJava UP-TO-DATE
:hello-world:processTestResources UP-TO-DATE
:hello-world:testClasses UP-TO-DATE
:hello-world:test UP-TO-DATE
:hello-world:check UP-TO-DATE
:hello-world:build

:hola-mundo:compileJava
:hola-mundo:processResources UP-TO-DATE
:hola-mundo:classes
:hola-mundo:jar
:hola-mundo:startScripts
:hola-mundo:distTar
:hola-mundo:distZip
:hola-mundo:assemble
:hola-mundo:compileTestJava UP-TO-DATE
:hola-mundo:processTestResources UP-TO-DATE
:hola-mundo:testClasses UP-TO-DATE
:hola-mundo:test UP-TO-DATE
:hola-mundo:check UP-TO-DATE
:hola-mundo:build

BUILD SUCCESSFUL

Total time: 5.544 secs

Como puede verse los tres proyectos se compilaron y se construyeron según las configuraciones realizadas. En el caso de "hello-world" y "hola-mundo" se generaron también los archivos comprimidos con los ejecutables de cada aplicación.

Para ejecutar las aplicaciones se puede usar el comando "./gradlew run", también desde el proyecto raíz:
:common:compileJava UP-TO-DATE
:common:processResources UP-TO-DATE
:common:classes UP-TO-DATE
:common:jar UP-TO-DATE

:hello-world:compileJava UP-TO-DATE
:hello-world:processResources UP-TO-DATE
:hello-world:classes UP-TO-DATE
:hello-world:run
hELLO wORLD

:hola-mundo:compileJava UP-TO-DATE
:hola-mundo:processResources UP-TO-DATE
:hola-mundo:classes UP-TO-DATE
:hola-mundo:run
hOLA mUNDO

BUILD SUCCESSFUL

Total time: 4.303 secs

El resultado es que primero se compila el proyecto "common" (en este caso aparece "UP-TO-DATE" ya que no fue necesario generar el JAR porque se acabó de ejecutar build) ya que se tiene la dependencia hacia este y posteriormente se ejecutan los proyectos "hello-world" y "hola-mundo" (se ejecutan en orden alfanumérico).

Referencias

http://gradle.org/docs/current/userguide/multi_project_builds.html

Más sobre Gradle

http://nombre-temp.blogspot.com/2016/01/tutorial-gradle.html

lunes, 13 de abril de 2015

Simplificando REST usando Retrofit

Retrofit

En un post anterior se habló de la librería OkHttp y como esta podía usarse como un potente y eficiente cliente HTTP, el cual inclusive permite realizar llamados asíncronos. Sin embargo, su uso está más enfocado a operaciones bajo nivel. En esta ocasión se hablará de Retrofit, una librería desarrollada por la misma compañía (Square Open Source) la cual facilita bastante el acceso a APIs REST, tanto JSON como XML, mediante el uso de interfaces y anotaciones, evitando la construcción y manipulación manual de peticiones HTTP.

Retrofit no depende de OkHttp, por defecto usará los componentes nativos de Java para los llamados HTTP. Sólo usará OkHttp si encuentra la librería en el classpath de la aplicación. Dado que anteriormente se mencionaron e ilustraron algunas de las ventajas de OkHttp, se incluirá esta librería.

Este cliente REST es completamente configurable, a tal punto que es posible adicionar interceptores para manipular headers en cada petición, realizar autenticación Basic o mediante OAuth, etc. Algunas de estas características se pueden encontrar en la página web de Retrofit: http://square.github.io/retrofit

Por defecto, esta librería realiza la serialización de JSON mediante la librería de Google Gson, sin embargo quienes prefieran usar Jackson, pueden hacerlo simplemente indicando que se usará el conversor que el equipo de Square desarrolló para ello: https://github.com/square/retrofit/tree/master/retrofit-converters

En esta ocasión se trabajarán dos ejemplos muy sencillos, ya que el gran poder que tiene Retrofit (en mi opinión) es la facilidad de su uso, lo cual es lo que se quiere demostrar en este post.

Ejemplo sencillo: consulta del clima

Para este ejemplo se consultará el servicio OpenWeatherMap el cual provee un API REST gratuito para consultar el estado del clima en las ciudades del mundo.

La siguiente consulta retornará el estado del clima en la ciudad de Londres:
http://api.openweathermap.org/data/2.5/weather?id=2643743
{  
  "coord":{  
    "lon":-0.13,
    "lat":51.51
  },
  "sys":{  
    "message":0.0344,
    "country":"GB",
    "sunrise":1428901694,
    "sunset":1428951224
  },
  "weather":[  
    {  
      "id":800,
      "main":"Clear",
      "description":"Sky is Clear",
      "icon":"01n"
    }
  ],
  "base":"stations",
  "main":{  
    "temp":276.272,
    "temp_min":276.272,
    "temp_max":276.272,
    "pressure":1037.14,
    "sea_level":1045.23,
    "grnd_level":1037.14,
    "humidity":87
  },
  "wind":{  
    "speed":1.11,
    "deg":10.0094
  },
  "clouds":{  
    "all":0
  },
  "dt":1428899601,
  "id":2643743,
  "name":"London",
  "cod":200
}

El ejemplo completo se puede encontrar en: https://github.com/guillermo-varela/retrofit-example

Lo primero entonces será crear el archivo de configuración de Gradle con las librerías necesarias.

Nota: quienes aún no conozcan del todo Gradle pueden consultar: http://nombre-temp.blogspot.com/2016/01/tutorial-gradle.html

build.gradle

plugins {
  id 'net.researchgate.release' version '2.0.2'
}

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'eclipse'

sourceCompatibility = 1.7
targetCompatibility = 1.7

mainClassName = 'com.blogspot.nombre_temp.retrofit.example.Example'

jar {
    manifest {
        attributes 'Implementation-Title': 'OkHttp Example', 'Implementation-Version': version
        attributes 'Main-Class': mainClassName
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'com.squareup.okhttp:okhttp:2.3.0'
    compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
    compile 'org.apache.commons:commons-lang3:3.4'
}

Como puede verse, además de las dependencias de Retrofit y OkHttp se agregaron también:

  • okhttp-urlconnection: permite la integración entre OkHttp y Retrofit
  • Apache Commons Lang3: Simplemente se agregó para facilitar el desarrollo de métodos "toString" que se usarán en este ejemplo. No se requiere para el funcionamiento de Retrofit.
Teniendo en cuenta la estructura de la respuesta JSON que retorna el servicio del clima, se procederá a desarrollar una clase con algunos de los atributos necesarios para almacenar esta información. Retrofit usará Gson para convertir el JSON que retorna el API en una instancia de esta clase. Es de notar que la clase no tiene todos los atributos que vienen en la respuesta JSON, esto se debe a que no es necesario tener todos los datos y se dejan solamente algunos para efectos del ejemplo:
package com.blogspot.nombre_temp.retrofit.example.model;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.gson.annotations.SerializedName;

public class WeatherData {

  private Main main;
  private Wind wind;

  public Main getMain() {
    return main;
  }
  public void setMain(Main main) {
    this.main = main;
  }
  public Wind getWind() {
    return wind;
  }
  public void setWind(Wind wind) {
    this.wind = wind;
  }

  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  }

  public static class Main {
    private float temp;
    @SerializedName("temp_min")
    private float tempMin;
    @SerializedName("temp_max")
    private float tempMax;
    private float pressure;
    private float humidity;

    public float getTemp() {
      return temp;
    }
    public void setTemp(float temp) {
      this.temp = temp;
    }
    public float getTempMin() {
      return tempMin;
    }
    public void setTempMin(float tempMin) {
      this.tempMin = tempMin;
    }
    public float getTempMax() {
      return tempMax;
    }
    public void setTempMax(float tempMax) {
      this.tempMax = tempMax;
    }
    public float getPressure() {
      return pressure;
    }
    public void setPressure(float pressure) {
      this.pressure = pressure;
    }
    public float getHumidity() {
      return humidity;
    }
    public void setHumidity(float humidity) {
      this.humidity = humidity;
    }
    @Override
    public String toString() {
      return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
  }

  public static class Wind {
    private float speed;
    private float deg;

    public float getSpeed() {
      return speed;
    }
    public void setSpeed(float speed) {
      this.speed = speed;
    }
    public float getDeg() {
      return deg;
    }
    public void setDeg(float deg) {
      this.deg = deg;
    }
    @Override
    public String toString() {
      return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
  }
}

La anotación "@SerializedName" se usa para indicar a Gson el nombre del dato que encontrará dentro del JSON.

A continuación se desarollará la interfaz Java con las operaciones del API REST que se usarán:
package com.blogspot.nombre_temp.retrofit.example.api;

import retrofit.Callback;
import retrofit.http.GET;
import retrofit.http.Query;

import com.blogspot.nombre_temp.retrofit.example.model.WeatherData;

public interface WeatherApi {

  @GET("/weather")
  WeatherData getWeather(@Query("id") long cityId);

  @GET("/weather")
  void getWeatherAsync(@Query("id") long cityId, Callback<Weatherdata> callback);
}

  • Método getWeather: Se indica mediante la anotación "@GET" que al llamar este método se realizará una petición GET a la operación "/weather" del API REST. Con la anotación "@Query" se está indicando que en la URL se debe agregar el parámetro "id" con el valor indicado en el parámetro "cityId", es decir si este método se llama con el parámetro "123" el llamado al API del clima será: http://api.openweathermap.org/data/2.5/weather?id=123. El JSON que retorne dicho llamado será convertido a una instancia de la clase "WeatherData".
  • Método getWeatherAsync: Es muy similar al anterior, con la diferencia de que este no retorna un resultado, ya que se trata de un llamado asíncrono. El procesamiento de la respuesta se dará en la instancia del objeto "Callback" que se indica en el segundo parámetro. Es de aclarar que no es la misma interfaz "com.squareup.okhttp.Callback" que se usó en el ejemplo de OkHttp, pero es el mismo principio.
Finalmente se tiene la clase que hace los llamados al API usando Retrofit:
package com.blogspot.nombre_temp.retrofit.example;

import java.util.Date;

import org.apache.commons.lang3.time.DateFormatUtils;

import retrofit.Callback;
import retrofit.RestAdapter;
import retrofit.RestAdapter.LogLevel;
import retrofit.RetrofitError;
import retrofit.client.Response;

import com.blogspot.nombre_temp.retrofit.example.api.WeatherApi;
import com.blogspot.nombre_temp.retrofit.example.model.WeatherData;

public class Example {

  private static final RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint("http://api.openweathermap.org/data/2.5")
    .setLogLevel(LogLevel.FULL)
    .build();

  private static final WeatherApi weatherService = restAdapter.create(WeatherApi.class);

  private static void print(String message) {
    System.out.println(message + ": " + DateFormatUtils.format(new Date(), "yyyy-MM-dd'T'HH:mm:ss.sss"));
  }

  public static void main(String[] args) {
    print("Starting main method");
    long londonId = 2643743L;

    WeatherData weatherData = weatherService.getWeather(londonId);
    print(String.format("Sync response [%s]", weatherData));

    weatherService.getWeatherAsync(londonId, new Callback<Weatherdata>() {
      @Override
      public void success(WeatherData weatherData, Response response) {
        print(String.format("Async response [%s]", weatherData));
      }
      @Override
      public void failure(RetrofitError error) {
        error.printStackTrace();
      }
    });

    print("Ending main method");
  }
}
  • Líneas 18-21: Se crea un "RestAdapter", el cual contiene las propiedades que tendrá el cliente REST a usar. En este caso se está indicando que use "LogLevel.FULL" sólo para propósitos de la demostración.
  • Línea 23: Se crea una instancia de la interfaz que se creó previamente con la definición de las operaciones del API. Es importante notar que en el código desarrollado no se hizo una clase que implementara dicha interfaz y definiera el cuerpo de sus métodos, esta es una tarea que Retrofit se encarga de hacer por nosotros.
  • Líneas 25-27: Definición de un método para que al imprimir un texto por consola se le adicione la fecha y hora actual. Esto con el fin de comprobar el comportamiento de las peticiones HTTP.
  • Líneas 29-48: Es el método main de la aplicación y allí se realiza:
    • Líneas 33-34: Llamado síncrono al API del clima y se imprime el resultado.
    • Líneas 36-45: Llamado asíncrono al API del clima. Se define también la clase anónima del tipo "Callback" en la cual se define cómo procesar la respuesta obtenida (método success) y cómo procesar un posible error en el llamado (método failure). Al igual que sucede con el ejemplo del post anterior sobre OkHttp, tanto el llamado HTTP al API REST, como el código dentro de la instancia de "Callback" se realizan en un hilo separado, es decir no se bloquea la ejecución del método main.
Al ejecutar esta clase se obtiene un resultado como el siguiente:
Starting main method: 2015-04-13T01:13:45.045
---> HTTP GET http://api.openweathermap.org/data/2.5/weather?id=2643743
---> END HTTP (no body)
<--- HTTP 200 http://api.openweathermap.org/data/2.5/weather?id=2643743 (735ms)
Server: nginx
Date: Mon, 13 Apr 2015 06:13:43 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Source: redis
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1428905625799
OkHttp-Received-Millis: 1428905625933

{"coord":{"lon":-0.13,"lat":51.51},"sys":{"message":0.0169,"country":"GB","sunrise":1428901694,"sunset":1428951224},"weather":[{"id":800,"main":"Clear","description":"Sky is Clear","icon":"01d"}],"base":"stations","main":{"temp":276.272,"temp_min":276.272,"temp_max":276.272,"pressure":1037.14,"sea_level":1045.23,"grnd_level":1037.14,"humidity":87},"wind":{"speed":1.11,"deg":10.0094},"clouds":{"all":0},"dt":1428904512,"id":2643743,"name":"London","cod":200}

<--- END HTTP (461-byte body)
Sync response [WeatherData[main=WeatherData.Main[temp=276.272,tempMin=276.272,tempMax=276.272,pressure=1037.14,humidity=87.0],wind=WeatherData.Wind[speed=1.11,deg=10.0094]]]: 2015-04-13T01:13:45.045
Ending main method: 2015-04-13T01:13:46.046
---> HTTP GET http://api.openweathermap.org/data/2.5/weather?id=2643743
---> END HTTP (no body)
<--- HTTP 200 http://api.openweathermap.org/data/2.5/weather?id=2643743 (122ms)
Server: nginx
Date: Mon, 13 Apr 2015 06:13:43 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Source: redis
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1428905626004
OkHttp-Received-Millis: 1428905626126

{"coord":{"lon":-0.13,"lat":51.51},"sys":{"message":0.0169,"country":"GB","sunrise":1428901694,"sunset":1428951224},"weather":[{"id":800,"main":"Clear","description":"Sky is Clear","icon":"01d"}],"base":"stations","main":{"temp":276.272,"temp_min":276.272,"temp_max":276.272,"pressure":1037.14,"sea_level":1045.23,"grnd_level":1037.14,"humidity":87},"wind":{"speed":1.11,"deg":10.0094},"clouds":{"all":0},"dt":1428904512,"id":2643743,"name":"London","cod":200}

<--- END HTTP (461-byte body)
Async response [WeatherData[main=WeatherData.Main[temp=276.272,tempMin=276.272,tempMax=276.272,pressure=1037.14,humidity=87.0],wind=WeatherData.Wind[speed=1.11,deg=10.0094]]]: 2015-04-13T01:13:46.046

Inicialmente se ve que se imprime la petición y la respuesta del llamado síncrono (letras verdes) y es sólo cuando termina dicho llamado que termina de ejecutarse el método main (letras azules). Ya después de terminado el método main es que aparece la petición y la respuesta del llamado asíncrono, con lo cual comprobamos que dicho llamado no ha bloqueado la ejecución de la aplicación, sino que se ha realizado en un hilo aparte.

Puede comprobarse también al comparar las respuestas en JSON y las representaciones String de los objetos "WeatherData" (resaltado en amarillo) que la conversión se ha realizado correctamente, sin que tampoco se haya tenido que procesar manualmente el JSON de la respuesta de API.

Nota: la aplicación termina de ejecutarse aproximadamente un minuto después de procesar la respuesta del llamado asíncrono, por la espera de 60 segundos para descartar los hilos del pool que se explicaba al final del post anterior sobre OkHttp.

Conclusión

Como puede verse en el resultado de la ejecución usando Retrofit (y dejando que el nivel de log sea FULL) ha sido la propia librería la encargada de generar todo el procesamiento JSON y HTTP requerido para consumir el API REST de una manera muy sencilla, simplemente definiendo una interfaz Java con anotaciones, inclusive de manera asíncrona sin tener que manipular hilos tampoco.

Referencias

http://square.github.io/retrofit
https://github.com/square/retrofit
http://openweathermap.org/api

sábado, 11 de abril de 2015

Peticiones HTTP asíncronas con OkHttp

OkHttp

Es una librería de código abierto la cual permite realizar operaciones tanto en HTTP como en SPDY de manera sencilla y eficiente en ambientes Java (versión 1.7 como mínimo) y Android (2.3 como mínimo), sin necesidad de cambiar el código de la aplicación entre ambas plataformas, con una interfaz fluida.

Entre las virtudes que tiene esta librería es que automáticamente evalúa las características de la conexión y determina cómo operar de mejor manera, por ejemplo si se tiene soporte para SPDY, contenido HTTP comprimido (GZIP), recuperación de algunos errores en la conexión, entre otras, la cuales se pueden encontrar en su página principal: http://square.github.io/okhttp

Internamente está usando Okio, el cual permite optimizar las operaciones I/O mediante "Non-blocking I/O" (NIO).

Actualmente se encuentra en la versión 2.3.0 y como puede verse en su registro de Maven Central, es un proyecto en activo desarrollo: http://mvnrepository.com/artifact/com.squareup.okhttp/okhttp

Ejemplo Simple

OkHttp permite realizar todas las operaciones HTTP. En este caso se realizará una petición GET, para demostrar lo sencillo que es usar esta librería:
import java.io.IOException;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class Example {

  private static final OkHttpClient client = new OkHttpClient();

  public static void main(String[] args) throws IOException {
    Request request = new Request.Builder()
      .url("http://www.textfiles.com/art/sunlogo.txt")
      .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
      throw new IOException("Error in the response: " + response);
    }

    System.out.println(response.body().string());
  }
}

Esto imprimirá en la consola el resultado de una petición GET a la URL "http://www.textfiles.com/art/sunlogo.txt", lo cual será un logo algo nostálgico.

En la documentación de la librería se pueden encontrar algunos ejemplos un poco más avanzados: https://github.com/square/okhttp/wiki/Recipes

Ejemplo Asíncrono

Como se mencionaba inicialmente, OkHttp permite realizar operaciones HTTP de manera asíncrona y es en este caso en el cual se desarrollará un ejemplo un poco más completo, para explicar y demostrar mejor esta característica.

Para este ejemplo se desarrollará una aplicación Java, la cual realice una petición HTTP, la cual demorará 10 segundos en responder. La idea es que el hilo principal (main en este caso) continúe su ejecución a pesar de que no se ha obtenido la respuesta de la petición (HTTP).

Se usará la librería MockServer para subir un servicio local, el cual tenga el retraso (delay) que se necesita para este caso.

El ejemplo completo se puede encontrar en: https://github.com/guillermo-varela/OkHttp-example

Lo primero entonces será crear el archivo de configuración de Gradle con las librerías necesarias.

Nota: quienes aún no conozcan del todo Gradle pueden consultar: http://nombre-temp.blogspot.com/2016/01/tutorial-gradle.html

build.gradle

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'eclipse'

sourceCompatibility = 1.7
targetCompatibility = 1.7
version = '1.0.0'

mainClassName = 'example.Example'

jar {
    manifest {
        attributes 'Implementation-Title': 'OkHttp Example', 'Implementation-Version': version
        attributes 'Main-Class': mainClassName
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.squareup.okhttp:okhttp:2.3.0'
    compile 'org.mock-server:mockserver-netty:3.9.6'
}

A continuación se tiene la clase con el código del servidor local y el petición asíncrona, junto con la explicación de lo que se hizo, aunque se tratará que el código sea lo más auto-documentado posible:
package example;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.model.Delay;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;

import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

public class Example {

  private static final OkHttpClient client = new OkHttpClient();
  private static final ClientAndServer mockServer = ClientAndServer.startClientAndServer(8080);

  static {
    mockServer.when(
        HttpRequest.request()
          .withPath("/test")
        )
        .respond(
            HttpResponse.response()
              .withBody("OK")
              .withDelay(new Delay(TimeUnit.SECONDS, 10))
        );

    client.setConnectTimeout(15, TimeUnit.SECONDS);
    client.setWriteTimeout(15, TimeUnit.SECONDS);
    client.setReadTimeout(15, TimeUnit.SECONDS);
  }

  private static void print(String message) {
    System.out.println(message + ": " + DateFormatUtils.format(new Date(), "yyyy-MM-dd'T'HH:mm:ss.sss"));
  }

  private static void doRequest() {
    print("Starting doRequest method");

    Request request = new Request.Builder()
      .url("http://localhost:8080/test")
      .build();

    client.newCall(request).enqueue(new Callback() {
      @Override
      public void onFailure(Request request, IOException e) {
        e.printStackTrace();
        stopMockServer();
      }

      @Override
      public void onResponse(Response response) throws IOException {
        print("Response [" + response.body().string() + "]");
        stopMockServer();
      }

      private void stopMockServer() {
        print("Stopping MockServer");
        if (mockServer != null) {
          mockServer.stop();
        }
      }
    });

    print("Ending doRequest method");
  }

  public static void main(String[] args) {
    print("Starting main method");
    doRequest();
    print("Ending main method");
  }
}
  • Línea 20: Se crea el cliente HTTP de la librería OkHttp. Este cliente puede ser compartido para todos los hilos de la aplicación, siempre y cuando no se cambie su configuración (timeouts, interceptores, etc.)
  • Línea 21: Se crea e inicia el servidor local de MockServer en el puerto 8080.
  • Líneas 24-32: Dentro del bloque estático, se configura MockServer para que todas las peticiones a "http://localhost:8080/test" tengan el texto "OK" y se produzca con un demora de 10 segundos.
  • Líneas 34-36: Se indica al cliente HTTP que tenga timeouts superiores al delay recién configurado.
  • Líneas 39-41: Definición de un método para que al imprimir un texto por consola se le adicione la fecha y hora actual. Esto con el fin de comprobar el comportamiento asíncrono de la petición HTTP.
  • Líneas 43-72: Es el método "doRequest" y es el que realiza la petición asíncrona usando el cliente HTTP de OkHttp (OkHttpClient).
    • Líneas 46-48: Se indica que la petición se hará a la URL "http://localhost:8080/test".
    • Línea 50: Se solicita al cliente HTTP (variable client) que cree un nuevo llamado usando el método "newCall", pero en lugar de ejecutarlo usando el método "execute" como se hizo en el primer ejemplo, se indica que se agregue a la cola de llamados asíncronos usando el método "enqueue", es decir no se está ejecutando el llamado HTTP, sino que se está programando para su futura ejecución, por lo cual es una operación que no bloquea la ejecución. Normalmente la ejecución del llamado HTTP se inicia inmediatamente, pero si hay otros llamados en espera de ejecución puede tomar un tiempo.
    • Líneas 51-69: Para indicar qué hacer cuando se obtenga la respuesta del llamado HTTP, se crea una clase anónima de la interfaz "Callback" en la cual se definen operaciones tanto en caso de obtener la respuesta (onResponse), como en caso de error (onFailure). En caso de que no se necesite la respuesta, estos métodos pueden quedar vacíos. Adicionalmente se desarrolló un método extra "stopMockServer" el cual detiene la ejecución del servidor local MockServer, y se invoca tanto caso de éxito como de error.
  • Líneas 74-78: Es el método main de la aplicación y allí simplemente se indica que se debe ejecutar el método "doRequest".
Al ejecutar esta clase se obtiene un resultado como el siguiente:

Starting main method: 2015-04-11T22:22:11.011
Starting doRequest method: 2015-04-11T22:22:11.011
Ending doRequest method: 2015-04-11T22:22:11.011
Ending main method: 2015-04-11T22:22:11.011
Response [OK]: 2015-04-11T22:22:22.022
Stopping MockServer: 2015-04-11T22:22:22.022

Como puede verse, la ejecución del método main (y del método doRequest) se realizó en menos de un milisegundo. Internamente OkHttp creó un hilo (dentro de un pool de hilos) y fue allí donde se procesó el llamado HTTP y se ejecutó el callback desarrollado.

Una manera adicional de comprobarlo es adicionando puntos de interrupción (breakpoints), uno al final del método "main" y otro en el método "onResponse" del callback:
Figura 1 - Hilo adicional para los llamados HTTP

De esta manera se puede, por ejemplo informar a un sistema externo que un usuario de nuestra aplicación a realizado alguna acción, sin generar demoras adicionales en la respuesta al usuario por dicho llamado. Esto sin necesidad de manipular manualmente hilos dentro del código de la aplicación.

Notas adicionales sobre los hilos de OkHttp

Referencias

http://square.github.io/okhttp
https://github.com/square/okhttp
https://github.com/square/okhttp/wiki/Recipes
http://square.github.io/okhttp/javadoc
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
http://www.mock-server.com

jueves, 9 de abril de 2015

Gradle - Versionamiento Automático

Introducción

En el post anterior se mostró cómo se puede configurar un proyecto (muy simple) para que use Gradle como herramienta de construcción. En esta ocasión se mostrará cómo usar Gradle para automatizar el versionamiento de los proyectos.

Se partirá usando como base el proyecto desarrollado en aquel post anterior, el cual se puede descargar desde: https://github.com/guillermo-varela/gradle-example/releases/tag/1.0.0

Quienes prefieran ver el resultado final, lo podrán descargar desde: https://github.com/guillermo-varela/gradle-example/releases/tag/1.0.1

Repositorio Maven

Tanto en los proyectos corporativos de las organizaciones como en proyectos de código abierto es relativamente común que los proyectos compilados y construidos (JAR, WAR, EAR, etc.) se almacenen en un repositorio Maven no solamente para tener el proyecto versionado, sino también en el caso de los proyectos que son librerías, para que puedan incluirse fácilmente en otros proyectos, indicando la versión específica que se quiere.

Para este ejemplo se usará una instalación local de Nexus, pero los conceptos pueden aplicarse a repositorios remotos, corporativos o públicos. Una vez descargado Nexus, para instalarlo se debe descomprimir el archivo obtenido en la carpeta de preferencia y dentro de la sub-carpeta "bin" ejecutar el comando que corresponda según el sistema operativo:

Linux/Mac: nexus install
Windows: nexus.bat install

Nota: En Windows los comandos de Nexus deben ejecutarse desde una línea de comandos como administrador.

Para iniciar Nexus se ejecuta el siguiente comando:

Linux/Mac: nexus start
Windows: nexus.bat start

Una vez iniciado, se puede acceder a la interfaz web mediante la URL: http://localhost:8081/nexus

Las credenciales por defecto como administrador son:
Usuario: admin
Contraseña: admin123

Figura 1 - Instalación local de Nexus

Gradle Maven

Es el plugin de Gradle que se usará para automatizar la publicación de las versiones del proyecto en el repositorio de Maven (el Nexus que se acabó de instalar).

Para usarlo, lo primero que se debe hacer es incluirlo y aplicarlo en el archivo de configuración del Gradle "build.gradle", agregando la siguiente instrucción:

apply plugin: 'maven'

Anteriormente se había visto cómo es posible crear propiedades dentro del mismo archivo "build.gradle", las cuales quedan disponibles como variables para el resto del archivo, como por ejemplo:

version = 1.0.0

En esta ocasión se creará el archivo "gradle.properties" en el cual se declararán algunas de estas propiedades y quedarán disponibles para usarlas dentro de "build.gradle":

version=1.0.1-SNAPSHOT
group=com.blogspot.nombre-temp

Aquí se incrementó la versión y se adicionó el sufijo "-SNAPSHOT", el cual es un estándar de Maven para indicar que es una versión que aún se encuentra en desarrollo y no hace parte de un "release" definitivo. Como su nombre en inglés lo indica, puede entenderse como la "foto" del proyecto en un determinado momento, a la cual Nexus le concatenará automáticamente la fecha en que se creó.

También se añadió la propiedad "group" la cual corresponderá al "groupId" del proyecto de Maven, mientras que "artifactId" será el mismo nombre del proyecto por defecto.

Ahora se deben indicar lo repositorios Maven a los que se deben subir las versiones, tanto "SNAPSHOT" como "RELEASE" (versiones definitivas) mediante la siguiente tarea de Gradle:
uploadArchives {
    repositories {
        mavenDeployer {
            snapshotRepository(url: "http://localhost:8081/nexus/content/repositories/snapshots/") {
                authentication(userName: "admin", password: "admin123")
            }
            repository(url: "http://localhost:8081/nexus/content/repositories/releases/") {
                authentication(userName: "admin", password: "admin123")
            }
        }
    }
}

Nota: Se usan los repositorios "snapshots" y "releases" porque ya vienen en la instalación de Nexus, sin embargo pueden crearse repositorios dentro de Nexus con cualquier nombre.

En este punto ya es posible ejecutar manualmente la nueva tarea de Gradle y subir el proyecto a Nexus. Si se deja en la versión el sufijo "-SNAPSHOT", el proyecto se subirá automáticamente al repositorio "snapshots", de lo contrario se usará "releases" por defecto. Para esto se usará el comando "gradlew uploadArchives":

gradlew uploadArchives

:compileJava
:processResources UP-TO-DATE
:classes
:jar
:startScripts
:distTar
:distZip
:uploadArchives
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1-SNAPSHOT/gradle-example-1.0.1-20150408.053732-1.jar to repository remote at http://localhost:8081/nexus/content/repositories/snapshots/
Transferring 1K from remote
Uploaded 1K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1-SNAPSHOT/gradle-example-1.0.1-20150408.053732-1.zip to repository remote at http://localhost:8081/nexus/content/repositories/snapshots/
Transferring 377K from remote
Uploaded 377K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1-SNAPSHOT/gradle-example-1.0.1-20150408.053732-1.tar to repository remote at http://localhost:8081/nexus/content/repositories/snapshots/
Transferring 420K from remote
Uploaded 420K

BUILD SUCCESSFUL

Total time: 15.108 secs

Figura 2 - Proyecto versionado en Nexus

Como puede verse en la figura 2, en Nexus quedan almacenados los archivos de la construcción del proyecto (JAR, ZIP, TAR) con la versión indicada (1.0.1-SNAPSHOT).

Repositorio Git

Para quienes tenga el proyecto desarrollado desde cero localmente, y no hayan creado un repositorio de Git, es un buen momento para crear uno. No necesariamente se debe crear un repositorio remoto en servidores como GitHub o Bitbucket, un repositorio local sirve para los propósitos de este post, y de hecho es una buena práctica en el desarrollo de software, ya que permite versionar los proyectos y devolver cambios más fácilmente.

Quienes sean completamente nuevos usando Git o sistemas de control de versiones en general, pueden revisar el siguiente tutorial, el cual no solamente explica cómo funciona sino también el porqué es un tema tan importante: http://readwrite.com/2013/09/30/understanding-github-a-journey-for-beginners-part-1

Para crear el repositorio local de Git lo único que se necesita es ejecutar desde la raíz del proyecto el siguiente comando:

git init
Initialized empty Git repository

Luego se debe modificar (o crear) el archivo ".gitignore", también en la raíz del proyecto:

.gradle/
build/

Estas dos líneas indican que las carpetas ".gradle" y "build" no se incluirán en el repositorio de código. En general no se recomienda incluir en los repositorios carpetas y archivos que se generen como parte del proceso de compilación/construcción del proyecto, mas no como parte del código como tal, ya que estos pueden generarse desde el equipo en el cual se esté trabajando, ahorrando espacio en el servidor remoto (en caso de tenerlo).

Un artículo que explica cómo se pueden determinar los archivos a incluir en un repositorio de código se puede encontrar en: http://blog.jooq.org/2014/09/08/look-no-further-the-final-answer-to-where-to-put-generated-code

Para indicar que los demás archivos se deben incluir en el repositorio se ejecuta el siguiente comando, incluyendo el punto del final:

git add .

Y finalmente para efectivamente incluir los archivos en el repositorio se ejecuta:

git commit -m "First commit."

Para marcar versiones definitivas (release) del proyecto en el repositorio Git se usará la funcionalidad de Tagging, la cual permite marcar un punto en la historia del repositorio como importante y por cada Tag creado crea una copia del estado del proyecto en ese momento y permite su descarga específica. Por ejemplo, quienes usaron el enlace para descargar el proyecto desde GitHub, pudieron ver que se está referenciando el tag "1.0.0".

Manualmente puede crearse un tag con el nombre "1.0.0" y el comentario "First tag" mediante el siguiente comando:

git tag -a "1.0.0"

Los tags creados en un repositorio pueden listarse mediante el comando:
git tag -l
1.0.0

Automatización del Versionamiento

Gradle-Release es el plugin que permite automatizar el versionamiento del código del proyecto en repositorios como Git, SVN, Mercurial, etc. Adicionalmente puede integrarse con el plugin de Maven anteriormente mencionado para que dicho versionamiento también sea automatizado.

Para usarlo, lo primero que se debe hacer es incluirlo y aplicarlo en el archivo de configuración del Gradle "build.gradle". Esto se debe hacer al principio del archivo y la sintaxis dependerá de la versión de Gradle que se esté usando:

Gradle 2.0 ó anterior:
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'net.researchgate:gradle-release:2.0.2'
    }
}
apply plugin: 'net.researchgate.release'

Gradle 2.1 ó posterior:
plugins {
  id 'net.researchgate.release' version '2.0.2'
}

Dado que en este ejemplo el proyecto está usando el Wrapper de Gradle con la versión 2.3, se asumirá que se usa la última sintaxis.

Para integrar este plugin con el de Maven, basta indicar que la creación de la versión "Release" en el repositorio de código (el tag) dependerá de que se haga lo mismo en Maven, relacionando las tareas de Gradle mediante la siguiente instrucción al final del archivo "build.gradle":

createReleaseTag.dependsOn uploadArchives

Este plugin se encarga de verificar, compilar, construir, versionar y publicar el proyecto por nosotros mediante el comando "gradlew release", sin embargo si se ejecuta en este momento el resultado tendrá un error:

gradlew release

:release
:gradle-example:initScmPlugin
:gradle-example:checkCommitNeeded FAILED
:release FAILED
Release process failed, reverting back any changes made by Release Plugin.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':checkCommitNeeded'.
> You have unversioned files:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
?? gradle.properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 7.419 secs

Básicamente se está indicando que se tienen cambios en el proyecto que aún no han sido incluidos en el repositorio de Git (commit). Este plugin dispone de múltiples configuraciones que permiten alterar su funcionamiento y en este caso se podría crear la propiedad "failOnCommitNeeded" con el valor "false" para que permita crear la nueva versión sin tener todos los cambios en el repositorio.

En este caso simplemente se incluirán los nuevos archivos (add), se guardarán los cambios (commit) y se publicarán en el repositorio remoto de Git (push) en caso de usar uno:

git add .
git commit -m "Maven and release plugins"
git push

Una vez hecho esto, ya debe ser posible la generación del nuevo release:

gradlew release
:release
:gradle-example:initScmPlugin
:gradle-example:checkCommitNeeded
:gradle-example:checkUpdateNeeded
:gradle-example:unSnapshotVersion
> Building 0% > :release > :gradle-example:confirmReleaseVersion
??> This release version: [1.0.1]
:gradle-example:confirmReleaseVersion
:gradle-example:checkSnapshotDependencies
:gradle-example:compileJava UP-TO-DATE
:gradle-example:processResources UP-TO-DATE
:gradle-example:classes UP-TO-DATE
:gradle-example:jar
:gradle-example:startScripts
:gradle-example:distTar
:gradle-example:distZip
:gradle-example:assemble
:gradle-example:compileTestJava UP-TO-DATE
:gradle-example:processTestResources UP-TO-DATE
:gradle-example:testClasses UP-TO-DATE
:gradle-example:test UP-TO-DATE
:gradle-example:check UP-TO-DATE
:gradle-example:build
> Building 0% > :release > :gradle-example:preTagCommit
Username for 'https://github.com': XXX
Password for 'https://XXX@github.com':
:gradle-example:preTagCommit
:gradle-example:uploadArchives
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1/gradle-example-1.0.1.jar to repository remote at http://localhost:8081/nexus/content/repositories/releases/
Transferring 1K from remote
Uploaded 1K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1/gradle-example-1.0.1.tar to repository remote at http://localhost:8081/nexus/content/repositories/releases/
Transferring 420K from remote
Uploaded 420K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1/gradle-example-1.0.1.zip to repository remote at http://localhost:8081/nexus/content/repositories/releases/
Transferring 377K from remote
Uploaded 377K
> Building 0% > :release > :gradle-example:createReleaseTag
Username for 'https://github.com': XXX
Password for 'https://XXX@github.com':
:gradle-example:createReleaseTag
> Building 0% > :release > :gradle-example
:updateVersion
??> Enter the next version (current one released as [1.0.1]): [1.0.2-SNAPSHOT]
:gradle-example:updateVersion
> Building 0% > :release > :gradle-example:commitNewVersion
Username for 'https://github.com': XXX
Password for 'https://XXX@github.com':
:gradle-example:commitNewVersion

BUILD SUCCESSFUL

Total time: 1 mins 12.978 secs

Han sucedido varias cosas con la ejecución de este comando:

  1. El plugin "Gradle Release" primero tomó el número la versión del proyecto indicado en el archivo "gradle.properties", automáticamente le quitó el sufijo "-SNAPSHOT" (ya que estamos generando una versión release) y pidió confirmar si esta es la versión a generar.
  2. Compilación, construcción y generación los archivos ejecutables del proyecto.
  3. Genera un commit en el repositorio Git.
  4. Dado que en este caso se estaba usando un repositorio Git remoto (GitHub), se solicitaron las credenciales para subir los cambios (push).
  5. Se cargaron los archivos de distribución (JAR, TAR, ZIP) en el repositorio "releases" de Nexus con la versión generada (1.0.1).

    Figura 3 - Versión release en Nexus

  6. Creación del tag en el repositorio Git. El tag usa como nombre la versión del proyecto. Nuevamente, como en este caso se tiene un repositorio remoto, se solicitaron las credenciales y se hizo el push.
    Figura 4 - Tags en GitHub

  7. Se presentó una sugerencia para el siguiente número de versión del proyecto y se pidió confirmarlo. Es de notar que el plugin automáticamente volvió a adicionar el sufijo "-SNAPSHOT",  ya que la versión release ya fue generada y publicada.
  8. Nuevo commit y push en el repositorio Git, dejando el proyecto listo para continuar su desarrollo.

En resumen, solamente ejecutando el comando "gradlew release" se ha podido generar una versión nueva del proyecto, publicando los archivos necesarios en los repositorios Maven y Git.

Como dato adicional, para quienes consideren que la manera de incrementar el número de la versión que usa el plugin es suficiente y no quieren confirmar la versión cada vez (o están usando un servidor de integración continua), pueden ejecutar lo siguiente:
gradlew release -Pgradle.release.useAutomaticVersion=true

O también indicarse desde el principio las versiones:
gradlew release -Pgradle.release.useAutomaticVersion=true -PreleaseVersion=1.0.0 -PnewVersion=1.1.0-SNAPSHOT


Para tener en cuenta

Quizás algunos piensan que se ha realizado demasiado trabajo para un proyecto tan pequeño. En primer lugar, un proyecto real es mucho más grande y complejo que el ejemplo presentado para estos posts, el cual se ha mantenido muy simple para centrar mejor la atención en los temas de Gradle, y a medida que los proyectos son más grandes se hacen más complejos de administrar, sin embargo los archivos de configuración de Gradle varían muy poco, por lo que es un esfuerzo de una sola vez, que en el mediano/largo plazo ahorra mucho trabajo.

Referencias

http://gradle.org/docs/current/userguide/maven_plugin.html
https://github.com/researchgate/gradle-release
http://readwrite.com/2013/09/30/understanding-github-a-journey-for-beginners-part-1
https://books.sonatype.com/nexus-book/reference/install.html
http://blog.jooq.org/2014/09/08/look-no-further-the-final-answer-to-where-to-put-generated-code

Más sobre Gradle


miércoles, 25 de marzo de 2015

Gradle - Introducción (Tutorial Básico)

¿Qué es Gradle?

Gradle es una herramienta para manejar y automatizar los procesos de compilación, construcción e inclusive despliegue de proyectos de software. Aunque inicialmente fue concebido para funcionar con proyectos Java y lenguajes de la máquina virtual de Java (Groovy, Scala) actualmente soporta plataformas y lenguajes adicionales como C/C++, JavaScript, etc. Actualmente ha sido la herramienta escogida para la gestión del código fuente de Android.

Gradle se desarrolló teniendo presente las ventajas que han traído las anteriores herramientas de construcción como el completo control del proceso de Apache Ant, la administración de dependencias de Ivy y la extensiblidad de Apache Maven, a la vez que se pretendía ofrecer ventajas sobre dichas herramientas como:
  • Scripts de construcción más fáciles de entender: Gradle usa un DSL basado en Groovy para el archivo de configuración del proyecto, el cual es más corto y más fácil de entender y mantener que los archivos XML tradicionales
Imagen tomada de http://www.drdobbs.com/jvm/why-build-your-java-projects-with-gradle/240168608
  • Un proceso de construcción más claro: basado en tareas secuenciales, ordenadas según las dependencias de cada tarea entre sí, en forma de grafo dirigido sin ciclos, para garantizar que se ejecutan en un orden correcto sin repetirse, pudiendo empezar desde cualquiera de las tareas (nodos) y Gradle sabe cual(es) debe ejecutar a partir de allí.

Imagen tomada de: http://en.wikipedia.org/wiki/Directed_acyclic_graph
  • Realiza una compilación y construcción parcial: en lugar de realizar siempre la compilación y construcción de todo el proyecto, Gradle usa construcciones incrementales (incremental builds), con lo cual solamente construye los módulos del proyecto que fueron modificados, haciendo que probar pequeños cambios en proyectos muy grandes sea más fácil y eficiente.
Existen más razones, pero para no hacer el post más largo de lo necesario se puede leer el siguiente artículo, en el cual se explican muy bien: http://www.drdobbs.com/jvm/why-build-your-java-projects-with-gradle/240168608

Ejemplo desde la Línea de Comandos (Terminal)

Para entender mejor cómo funciona Gradle y cómo se puede usar en proyectos Java, es mejor empezar con un ejemplo básico desde la línea de comandos, en un siguiente post se mostrará como hacerlo desde el IDE Eclipse.

La aplicación de ejemplo se puede descargar desde: https://github.com/guillermo-varela/gradle-example/releases/tag/1.0.0

  1. Descargar Gradle desde https://www.gradle.org/downloads. Nota: solamente se requieren los binarios.
  2. Descomprimir el archivo descargado en la ruta de preferencia.
  3. Para mayor comodidad se recomienda agregar de la instalación de Gradle al "PATH" del sistema operativo: <ruta_gradle>/bin
  4. Abrir una instancia de la línea de comandos (Terminal) y comprobar que la instalación de Gradle funciona correctamente mediante el comando "gradle -v" el cual debe tener una respuesta como la siguiente:
    ------------------------------------------------------------
    Gradle 2.3
    ------------------------------------------------------------
    
    Build time:   2015-02-16 05:09:33 UTC
    Build number: none
    Revision:     586be72bf6e3df1ee7676d1f2a3afd9157341274
    
    Groovy:       2.3.9
    Ant:          Apache Ant(TM) version 1.9.3 compiled on December 23 2013
    JVM:          1.8.0_40 (Oracle Corporation 25.40-b25)
    OS:           Linux 3.16.0-31-generic amd64
    
    
  5. Crear la siguiente estructura de carpetas, correspondientes al proyecto Java (se usa el estándar de carpetas de Maven, ya que es muy usado aún entre las personas que usan Gradle, a pesar de que puede usarse una estructura personalizada):
    gradle-example/
    ..src/
    ....main/
    ......java/
    ........example/
    
  6. Dentro de la carpeta "example" crear el archivo "Example.java" con el siguiente contenido:

    package example;
    
    public class Example {
      public static void main(String[] args) {
        System.out.println("This is an example");
      }
    }
     
  7. Hasta ahora se tiene un proyecto muy simple de Java (solamente imprime una cadena). Ahora se debe crear el archivo de configuración de gradle llamado "build.gradle" en la raíz del proyecto (carpeta "gradle-example"):
    apply plugin: 'java'
    apply plugin: 'application'
    
    version = '1.0.0'
    sourceCompatibility = 1.6
    targetCompatibility = 1.6
    
    mainClassName = 'example.Example'
    
    jar {
        manifest {
            attributes 'Implementation-Title': 'Gradle Example', 'Implementation-Version': version
            attributes 'Main-Class': mainClassName
        }
    }
    
    Con la configuración anterior ya se le está indicando a Gradle que:
    • Debe construir el proyecto como un proyecto Java.
    • Se aplica el plugin "application", el cual habilita varias opciones (comandos) que permiten ejecutar y construir el proyecto como una aplicación Java.
    • Se trata de la versión "1.0.0".
    • La versión de Java con la que se debe compilar el proyecto es "1.6".
    • La clase con el método "main" (punto de entrada de la aplicación) será "example.Example".
    • La aplicación se empaquetará en un archivo "JAR".
    • El archivo "META-INF/MANIFEST.MF" que estará dentro del JAR tendrá las propiedades del nombre de la aplicación, la versión y el nombre completo de la clase con el método "main". Cabe anotar que para la versión no se puso el valor "1.0.0" nuevamente, sino que se usó la variable (propiedad) "version" creada anteriormente en el archivo. Como se mencionaba anteriormente, Gradle usar un DSL, es decir el archivo de configuración en un programa. De igual manera se reutilizó la propiedad "mainClassName". A manera de ejercicio se deja la prueba de adicionar la instrucción "println 'Hello World'" al archivo "build.gradle", sólo para ver que pasa en la próxima construcción. Esta característica de los archivos de configuración Gradle permiten que el proceso de construcción sea dinámico y se tomen decisiones dependiendo de variables, ambientes, etc.

  8. Para ver los diferentes comandos y opciones que ahora tenemos disponibles para el proyecto con Gradle se puede ejecutar el comando "gradle tasks" desde la raíz del proyecto:
    :tasks
    
    ------------------------------------------------------------
    All tasks runnable from root project
    ------------------------------------------------------------
    
    Application tasks
    -----------------
    installApp - Installs the project as a JVM application along with libs and OS specific scripts.
    run - Runs this project as a JVM application
    
    Build tasks
    -----------
    assemble - Assembles the outputs of this project.
    build - Assembles and tests this project.
    buildDependents - Assembles and tests this project and all projects that depend on it.
    buildNeeded - Assembles and tests this project and all projects it depends on.
    classes - Assembles classes 'main'.
    clean - Deletes the build directory.
    jar - Assembles a jar archive containing the main classes.
    testClasses - Assembles classes 'test'.
    
    Build Setup tasks
    -----------------
    init - Initializes a new Gradle build. [incubating]
    
    Distribution tasks
    ------------------
    assembleMainDist - Assembles the main distributions
    distTar - Bundles the project as a distribution.
    distZip - Bundles the project as a distribution.
    installDist - Installs the project as a distribution as-is.
    
    Documentation tasks
    -------------------
    javadoc - Generates Javadoc API documentation for the main source code.
    
    Help tasks
    ----------
    components - Displays the components produced by root project 'gradle-example'. [incubating]
    dependencies - Displays all dependencies declared in root project 'gradle-example'.
    dependencyInsight - Displays the insight into a specific dependency in root project 'gradle-example'.
    help - Displays a help message.
    projects - Displays the sub-projects of root project 'gradle-example'.
    properties - Displays the properties of root project 'gradle-example'.
    tasks - Displays the tasks runnable from root project 'gradle-example'.
    
    Verification tasks
    ------------------
    check - Runs all checks.
    test - Runs the unit tests.
    
    Other tasks
    -----------
    wrapper
    
    Rules
    -----
    Pattern: clean: Cleans the output files of a task.
    Pattern: build: Assembles the artifacts of a configuration.
    Pattern: upload: Assembles and uploads the artifacts belonging to a configuration.
    
    To see all tasks and more detail, run gradle tasks --all
    
    To see more detail about a task, run gradle help --task 
    
    BUILD SUCCESSFUL
    
    Total time: 3.859 secs
    
  9. Según se pudo ver en el punto anterior, ya se tiene disponible la tarea "run", la cual permitirá ejecutar el proyecto como una aplicación Java, mediante el comando "gradle run":
    :compileJava UP-TO-DATE
    :processResources UP-TO-DATE
    :classes
    :run
    This is an example
    
    BUILD SUCCESSFUL
    
    Total time: 3.583 secs
    
    Como puede verse, la tarea "run" sólo se ejecutó hasta el final, cuando Gradle ejecutó las tareas de las que se dependía, y como resultado se ejecutó el código de la aplicación (imprimió la cadena "This is an example").

  10. Adicionalmente se tiene disponible también la tarea "build", la cual construirá el proyecto y ejecutará las pruebas unitarias (en caso de tenerlas), mediante el siguiente comando "gradle build":
    :compileJava UP-TO-DATE
    :processResources UP-TO-DATE
    :classes UP-TO-DATE
    :jar
    :startScripts
    :distTar
    :distZip
    :assemble
    :compileTestJava UP-TO-DATE
    :processTestResources UP-TO-DATE
    :testClasses UP-TO-DATE
    :test UP-TO-DATE
    :check UP-TO-DATE
    :build
  11. En este punto Gradle debe haber creado la siguiente estructura de carpetas adicionales:
    gradle-example/
    ..".gradle/"
    ..build/
    ....classes/
    ....dependency-cache/
    ....distributions/
    ......gradle-example-1.0.0.tar
    ......gradle-example-1.0.0.zip
    ....libs/
    ......gradle-example-1.0.0.jar
    ....scripts/
    ......gradle-example
    ......gradle-example.bat
    ....tmp/
    
    Realmente lo destacable en este punto son las carpetas:

    • "libs" en la cual se almacena el JAR generado para la aplicación "gradle-example-1.0.0".
    • "distributions" en la cual se almacenan los archivos (TAR y ZIP como opciones) para distribuir la aplicación. La diferencia con el JAR generado en la carpeta "llibs" es que estos archivos comprimidos contienen el JAR de la aplicación y también las dependencias y demás archivos necesarios. Esto quedará un poco más claro cuando se agregue una dependencia más adelante.
    • "scripts" en la cual se almacenan scripts en Bash (para sistemas basados en Unix) y Batch (para sistemas Windows), los cuales permiten ejecutar la aplicación solamente con ejecutar el script. Los parámetros necesarios (como el classpath) ya se agregan en cada uno de los scripts. Estos archivos también se encuentran dentro de los archivos comprimidos en "distributions".

  12. La aplicación ahora se puede ejecutar de dos maneras:
    1. Directamente desde el JAR:
      java -jar build/libs/gradle-example-1.0.0.jar
      This is an example
      
    2. Descomprimiendo alguno de los archivos comprimidos en "distributions" y ejecutando el script correspondiente al sistema operativo usado:
      build/distributions/gradle-example-1.0.0/bin/gradle-example
      This is an example
      

Gradle Wrapper

Esta es una opción que facilita la construcción con Gradle, tanto el código compartido entre un equipo de trabajo como en servidores de entrega continua. Básicamente es un conjunto de scripts (batch y shell), los cuales permiten ejecutar la construcción con Gradle, a pesar de que el computador actual no tenga instalado Gradle. Para usarlo se debe agregar la siguiente entrada al archivo "build.gradle":
task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}
Este fragmento está creando la tarea de Gradle "wrapper" y se está indicando que la versión de Gradle a usar es "2.3". Esta tarea se ejecuta como cualquier otra tarea de Gradle: "gradle wrapper".

Una vez ejecutada la tarea, se crea la siguiente estructura:

gradle-example/
..gradle/
....wrapper/
......gradle-wrapper.jar
......gradle-wrapper.properties
..gradlew
..gradlew.bat

Los archivos "gradlew" y "gradle.bat" son los scripts shell y batch que se mencionaron previamente y se encargan de ejecutar el archivo "gradle-wrapper.jar", el cual a su vez descarga, descomprime y ejecuta la versión de Gradle que se indicó en la tarea, y que queda también en el archivo "gradle-wrapper.properties", de esta manera también se garantiza que todas las construcciones se van a realizar con la misma versión de Gradle. Una vez generados estos archivos, no es necesario volver a ejecutar la tarea "wrapper".

Todos estos archivos deben ser almacenados junto con el proyecto en el repositorio de código, precisamente para que las demás personas del equipo o sistemas de construcción automatizado usen los mismos archivos para la construcción.

Este wrapper permite ejecutar las mismas tareas que están disponibles para el comando "gradle", desde la raíz del proyecto, simplemente ejecutando el comando "gradlew". Por ejemplo, para ejecutar la construcción el comando sería "gradlew build" (aunque en sistemas Linux posiblemente se requiera usar "./gradlew build"):

gradlew build 
Downloading https://services.gradle.org/distributions/gradle-2.3-bin.zip
........................................................................
Unzipping /.../.gradle/wrapper/dists/gradle-2.3-bin/.../gradle-2.3-bin.zip to /.../.gradle/wrapper/dists/gradle-2.3-bin/...
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build UP-TO-DATE

BUILD SUCCESSFUL

Total time: 1 mins 29.447 secs 

Nota: En caso de querer actualizar la versión de Gradle que se usará mediante el Wrapper, solamente se requiere editar los valores correspondientes en el archivo "gradle-wrapper.properties", aunque si se desea usar alguna funcionalidad específica de una versión de Gradle, es posible que sea necesario volver a ejecutar la tarea "wrapper".

Dependencias

Gradle permite trabajar con dependencias tanto agregadas manualmente como archivos dentro del mismo proyecto (Flat directory repository), como con repositorios externos (públicos y privados) de Ivy y Maven.

Para mantener el ejemplo sencillo, se usará una funcionalidad simple de Apache Commons:
package example;
import org.apache.commons.lang3.StringUtils;

public class Example {
  public static void main(String[] args) {
    System.out.println("This is an example");
    System.out.println(StringUtils.swapCase("This is an example"));
  }
}
La librería se descargará desde el repositorio central de Maven, para lo cual se agregará lo siguiente al archivo "build.gradle":
repositories {
    mavenCentral()
}
dependencies {
    compile 'org.apache.commons:commons-lang3:3.3.2'
}

Dado que el repositorio central de Maven es un repositorio muy usado, Gradle ya tiene una función que permite indicarlo fácilmente, sin indicar URL. En este caso se agregó la librería Apache Commons como una dependencia necesaria para compilar el proyecto. Existen otras opciones (configuraciones) que permiten indicar dependencias sólo para pruebas (testCompile) entre otras.

De esta manera ya es posible volver a ejecutar el comando "gradlew build", lo cual generará de nuevo el JAR de la aplicación, sin embargo el JAR no tendrá la librería de Apache Commons, al igual que otros archivos JAR solamente contiene los archivos compilados de la propia aplicación. Las librerías externas deberán estar en una carpeta que se indique como parte del classpath:

java -cp "path_to_library/commons-lang3-3.3.2.jar:build/libs/gradle-example-1.0.0.jar" example.Example 

This is an example
tHIS IS AN EXAMPLE

Aquí es donde está la utilidad de los archivos comprimidos en "distributions", ya que estos, como se mencionó anteriormente, contienen las dependencias necesarias para ejecutar la aplicación de una manera más sencilla:
gradle-example-1.0.0.zip
..gradle-example-1.0.0
....bin
......gradle-example
......gradle-example.bat
....lib
......commons-lang3-3.3.2.jar
......gradle-example-1.0.0.jar

Basta con descomprimir alguno de los archivo y ejecutar:
build/distributions/gradle-example-1.0.0/bin/gradle-example
This is an example
tHIS IS AN EXAMPLE

JAR Auto-Contenido

En caso de que se quiera/necesite un solo archivo para ejecutar la aplicación, existe una manera de generar un JAR auto-contenido con todas las librerías necesarias para funcionar por su cuenta.

Para ello se creará una tarea adicional en "build.gradle" indicando que el JAR a generar debe contener las dependencias del proyecto (comúnmente llamado "fat JAR"). Aunque se puede agregar esta opción en la tarea "jar" se crea una nueva para dejar la posibilidad de generar el JAR sin las dependencias.
//create a single Jar with all dependencies
task fatJar(type: Jar) {
    baseName = project.name + '-all'
    manifest {
        attributes 'Implementation-Title': 'Gradle Example - All', 'Implementation-Version': version
        attributes 'Main-Class': mainClassName
    }
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}
En este caso el nombre del JAR será el nombre del proyecto con el sufijo "-all" (sólo para demostrar una manera de modificar el nombre por defecto del archivo), se indicó con la instrucción "from" que debe usar las dependencias de compilación (configuración compile) y que debe incluir también los archivos compilados de este proyecto (con la instrucción "with jar").

gradlew fatJar
:compileJava
:processResources UP-TO-DATE
:classes
:fatJar

BUILD SUCCESSFUL

Total time: 5.05 secs

Ahora ejecutar la aplicación será mucho más fácil desde el JAR:
java -jar build/libs/gradle-example-all-1.0.0.jar
This is an example
tHIS IS AN EXAMPLE

Esto es básicamente Gradle. En general los IDEs tienen una integración más desarrollada, pero como introducción ayuda mucho conocer cómo funciona la herramienta, sin los atajos que muchas veces tienen dichos IDEs.

Referencias