sábado, 28 de noviembre de 2015

Aplicaciones Web Java usando Gradle y el plugin Gretty

Introducción

En un post anterior (el cual se recomienda leer antes de continuar) se mostró cómo se pueden desarrollar aplicaciones web usando Gradle y su plugin oficial Jetty. Aunque tiene algunos beneficios, también se evidenciaron las falencias que aún tiene dicho plugin.

Es por ello que en esta ocasión se mostrará cómo usar el plugin Gretty, el cual también permite el desarrollo de aplicaciones web con Java y Gradle, pero con varias características que lo hacen una alternativa moderna y mucho más práctica que el plugin Jetty.

La principal ventaja que tiene Gretty es que soporta Jetty Embebido en sus versiones 7, 8 y 9 e inclusive también soporta Tomcat Embebido 7 y 8, por lo cual pueden usarse funcionalidades más recientes y las especificaciones Servlet 3 y JSP 2.2. Las versiones exactas de los servidores y especificaciones soportadas se pueden encontrar en el archivo "gradle.properties" de Gretty.

Al igual que en aquel post anterior, se desarrollará un pequeño proyecto web con dos páginas JSP y un Servlet, pero esta vez usando Jetty 9 y Servlet 3.

El proyecto completo se puede descargar desde: https://github.com/guillermo-varela/webapp-gradle-gretty-example

Dependencias y Configuración Gradle

build.gradle

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

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse-wtp'
apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin'

sourceCompatibility = 1.7
targetCompatibility = 1.7

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

repositories {
    jcenter()
}

dependencies {
    providedCompile "javax.servlet.jsp:jsp-api:2.2"
    providedCompile "javax.servlet:javax.servlet-api:3.0.1"
    providedCompile "javax.el:el-api:2.2"
    compile "javax.servlet:jstl:1.2"
}

En general este archivo es muy similar al que se tenía en el post sobre el plugin Jetty, con la diferencia de que en la línea 8 se aplica el plugin Gretty y en la sección de dependencias se usan las versiones actualizadas.

Por defecto Gretty usa Jetty 9, por lo cual no hace falta indicarlo explícitamente, pero por ejemplo si se quisiera usar Tomcat 8 lo único que tendría que hacerse es agregar el siguiente fragmento a "build.gradle":

gretty {
  servletContainer = 'tomcat8'
}

Actualmente los valores permitidos son: jetty7, jetty8, jetty9, tomcat7 y tomcat8

gradle.properties

version=1.0.0-SNAPSHOT

Servlet

ExampleServlet.java

Simplemente pondrá un objeto Date en la petición procesada e indicará que se muestre la página "example.jsp"

package com.blogspot.nombre_temp.webapp.gradle.jetty.example;

import java.io.IOException;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "exampleServlet", urlPatterns = {"/example"})
public class ExampleServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("currentDate", new Date());
        request.getRequestDispatcher("example.jsp").forward(request, response);
    }
}

La anotación "WebServlet" fue introducida en la especificación Servlet 3.0 para permitir indicar como se debe ejecutar el Servlet de manera equivalente al Descriptor de Despliegue (Deployment Descriptor), el cual ya no es requerido para aplicaciones y servidores que soporten esta nueva versión de la especificación. Aquí el atributo "urlPatterns" indica que recibirá peticiones que lleguen a la ruta "/example", por ejemplo http://localhost:8080/webapp-gradle-gretty-example/example.

Para ver otros cambios en Servlet 3.0 puede consultarse: https://community.oracle.com/docs/DOC-983211

Contenido Web

Con Gretty también se requiere crear manualmente la carpeta "src/main/webapp/" y a su vez dentro de esta se crearán los siguientes archivos:

index.jsp

Será una página sencilla para verificar que la aplicación está funcionando.
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" session="false" %>
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title>Webapp Gradle Gretty Example</title>
</head>

<body>
    <h1>
        Hello World
    </h1>
</body>
</html>

example.jsp

Mostrará la fecha y hora a partir del objeto Date enviado por "ExampleServlet" usando el formato indicado en el tag JSTL.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" session="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Webapp Gradle Gretty Example</title>
</head>

<body>
    <h1>
        Current Date: <fmt:formatDate value="${currentDate}" pattern="yyyy-MM-dd HH:mm:ss" />
    </h1>
</body>
</html>

Nota: quienes usan Eclipse y aplicaron el plugin "eclipse-wtp" debe asegurarse de que la carpeta "src/main/webapp/" sea incluida en "Deployment Assembly", lo cual puede hacerse manualmente haciendo clic derecho en el proyecto y seleccionando "Properties" -> "Deployment Assembly" o actualizando el proyecto Gradle usando "Buildship" o "Gradle Integration for Eclipse". En caso de que aparezcan errores cargando el "Deployment Descriptor" (web.xml), basta con cerrar el proyecto y abrirlo de nuevo en Eclipse.

Figura 1 - Deployment Assembly con la carpeta webapp

Ejecutar la Aplicación

Desde Eclipse

Quienes usan Eclipse y aplicaron el plugin "eclipse-wtp" pueden usar exactamente las mismas instrucciones indicadas en el post anterior.

Desde Gretty

Gretty tiene dos pares de tareas que permiten ejecutar la aplicación:

  • "appRun" y "appRunWar", las cuales deben ejecutarse desde la línea de comandos (terminal) ya que se inician en modo interactivo (desde una carpeta o el WAR generado correspondientemente), con lo cual al presionar "Enter" se detiene el servidor, sin necesidad de ejecutar otra tarea.
./gradlew appRun
:prepareInplaceWebAppFolder
:createInplaceWebAppFolder
:compileJava
:processResources
:classes
:prepareInplaceWebAppClasses
:prepareInplaceWebApp
:appRun
22:58:29 INFO  Jetty 9.2.10.v20150310 started and listening on port 8080
22:58:29 INFO  webapp-gradle-gretty-example runs at:
22:58:29 INFO    http://localhost:8080/webapp-gradle-gretty-example
Press any key to stop the server.
> Building 87% > :appRun

  • "appStart" y "appStartWar", las cuales pueden ejecutarse no sólo desde la línea de comandos, sino también desde el IDE que se esté usando. Para detener el servidor se deberá ejecutar la tarea "appStop".
./gradlew appStart
:prepareInplaceWebAppFolder
:createInplaceWebAppFolder
:compileJava
:processResources
:classes
:prepareInplaceWebAppClasses
:prepareInplaceWebApp
:appStart
23:04:35 INFO  Jetty 9.2.10.v20150310 started and listening on port 8080
23:04:35 INFO  webapp-gradle-gretty-example runs at:
23:04:35 INFO    http://localhost:8080/webapp-gradle-gretty-example
Run 'gradle appStop' to stop the server.
> Building 87% > :appStart

Figura 2 - JSP con la hora actual desde el Servlet señalando la versión de Jetty

Como puede verse en la figura 2, la aplicación funciona igual a su contra-parte que usaba el plugin Jetty, pero ahora puede verse que se está usando Jetty 9.

Depurando la Aplicación (Debug)

En caso de usar un servidor configurado en Eclipse, pueden usar exactamente las mismas instrucciones indicadas en el post anterior.

Gretty sí tiene una diferencia con respecto al plugin Jetty y es que sí incluye dos tareas para iniciar el servidor en modo debug, sin necesidad de tener que crear la variable de entorno "GRADLE_OPTS", y corresponden a los equivalentes de las tareas para ejecutar la aplicación: "appRunDebug", "appRunWarDebug", "appStartDebug" y "appStartWarDebug":

./gradlew appStartDebug
:prepareInplaceWebAppFolder
:createInplaceWebAppFolder
:compileJava
:processResources
:classes
:prepareInplaceWebAppClasses
:prepareInplaceWebApp
:appStartDebug
Listening for transport dt_socket at address: 5005

Estas tareas inician la aplicación y la suspenden mientras se conecta un proceso de debug al puerto 5005. Para más información sobre cómo iniciar dicho proceso desde Eclipse puede consultarse la sección "Depurar una aplicación Java (Debug)" del post sobre "Gradle Integration for Eclipse" o sobre "Buildship".

Para detener la aplicación puede presionarse "Enter", en caso de usar una tarea en modo interactivo o ejecutando la tarea "appStop".

Generando el WAR

Dado que aún se usa el plugin War, la generación del archivo WAR se realiza ejecutando la tarea de Gradle "build", lo cual creará el archivo en la carpeta "build/libs".

Generar un producto

Una característica adicional que tiene Gretty es la posibilidad de generar un producto, lo cual consiste en un conjunto de archivos, librerías y scripts que permiten ejecutar directamente la aplicación sin necesidad de desplegar el WAR en un contenedor web. El producto puede generarse usando la tarea de Gradle "buildProduct".

./gradlew buildProduct
:compileJava
:processResources
:classes
:war
:assemble
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build
:buildProduct

BUILD SUCCESSFUL

Total time: 15.335 secs

Al finalizar se tendrá una carpeta con el nombre del proyecto en "build/output"

├───build/
│   ├───output/
│   │   └───webapp-gradle-gretty-example/
│   │       ├───conf/
│   │       ├───runner/
│   │       ├───starter/
│   │       ├───temp/
│   │       ├───webapps/
│   │       └───webapps-exploded/
│   │       └───restart.bat
│   │       └───restart.sh
│   │       └───run.bat
│   │       └───run.sh
│   │       └───start.bat
│   │       └───start.sh
│   │       └───stop.bat
│   │       └───stop.sh
│   │       └───VERSION.txt

Los scripts "run" y "start" (.bat o .sh según el sistema operativo) permiten iniciar el contenedor web  embebido configurado para el proyecto (Jetty 9 en este caso). El primer script inicia el servidor en una consola interactiva permitiendo finalizarlo al presionar "Enter", mientras que el segundo requiere ejecutar el script "stop".

Nota: En caso de que al iniciar el servidor e intentar acceder a la aplicación web aparezca el error "PWC6345: There is an error in invoking javac. A full JDK (not just JRE) is required", se debe verificar que el ejecutable "java" usado por el sistema operativo es el del JDK. En Windows puede usarse el comando "where java", mientras que en sistemas Linux y Mac "which java", y corregir la variable de entorno "PATH" según corresponda.

Conclusiones

Como pudo verse Gretty no solamente tiene más funcionalidades que el plugin Jetty de Gradle, sino que es más fácil de usar (no requiere variables de entorno para el modo debug), por ende es preferible el uso de Gretty.

Aquí sólo se mostraron las funcionalidades más básicas de Gretty, por lo cual se recomienda consultar su documentación oficial para revisar algunas más avanzadas como por ejemplo el soporte para HTTPS, seguridad en Jetty y Tomcat o el soporte para múltiples aplicaciones web (farm).

Como se indicó al inicio, las versiones exactas de Jetty y Tomcat vienen pre-definidas en el plugin, por lo cual aunque se garantiza que todos los desarrolladores usarán la misma versión del contenedor web, esta puede no coincidir con la que se tenga en el ambiente de producción.

Quienes usen Tomcat y quieran garantizar el uso de la misma versión en todos los ambientes, tienen la opción de usar "gradle-tomcat-plugin" aunque de momento no se tiene soporte completo para Tomcat 8: https://github.com/bmuschko/gradle-tomcat-plugin/issues/73

Por lo demás Gretty es un plugin bastante completo y cumple con las necesidades para desarrollar fácil y cómodamente aplicaciones web con Java y Gradle.

Referencias

Aplicaciones Web Java usando Gradle y el plugin Jetty

Introducción

En un post anterior se mostró cómo es posible desarrollar un API REST con Java usando Gradle y Jetty embebido. En esta ocasión se desarrollará una aplicación web un poco más tradicional, con un Serlvet y un JSP y teniendo como archivo final de la aplicación un WAR, también usando Java y Gradle.

Dado que para este proyecto se tendrá un archivo WAR, el cual se despliega (deploy) en un Contenedor Web (Web Container o Servlet Container), no se usará Jetty embebido de la misma manera que se usó en aquel post anterior sino que se usará el plugin oficial de Gradle Jetty, el cual sirve para desplegar la aplicación localmente y probarla a medida que se está desarrollando, es decir sólo se usa para pruebas durante el desarrollo.

Dicho plugin tiene una restricción y es que sólo soporta Jetty 6 (a la fecha Jetty se encuentra en la versión 9). Como lo indica la documentación de Jetty, la versión 6 sólo soporta la especificación Servlet 2.5 y JSP 2.0, por lo cual los proyectos que usen este plugin deben usar esas mismas versiones de estas especificaciones.

Para desarrollo de aplicaciones con especificaciones actuales pueden usarse alternativas como Gretty.

El proyecto completo se puede descargar desde: https://github.com/guillermo-varela/webapp-gradle-jetty-example

Dependencias y Configuración Gradle

build.gradle

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

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse-wtp'
apply plugin: 'jetty'

sourceCompatibility = 1.7
targetCompatibility = 1.7

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

repositories {
    jcenter()
}

dependencies {
    providedCompile "javax.servlet:servlet-api:2.5"
    providedCompile "javax.servlet:jsp-api:2.0"
    providedCompile "javax.el:el-api:2.2"
    compile "jstl:jstl:1.2"
}

httpPort=8080
stopPort=9090
stopKey="stopKey"

En la línea 6 se aplica el plugin war, el cual:

  • Hace que el archivo generado luego de compilar el proyecto sea un WAR dentro de "build/libs".
  • Permite que el contenido de la carpeta "src/main/webapp/" haga parte del proceso de construcción y se toma como el contenido web (páginas, estilos CSS, Javascript, imágenes, etc.). Nota: el plugin no crea dicha carpeta, debe ser creada manualmente.
  • Adiciona las configuraciones "providedCompile" y "providedRuntime" para la administración de dependencias, las cuales permiten incluir dependencias que se requieren para la compilación del proyecto pero no requieren incluirse dentro del WAR final ya que el Contenedor Web se encarga de proveerlas.
En la línea 7 se aplica el plugin eclipse-wtp, el cual es completamente opcional (aún si se está usando Eclipse) y se usa para indicarle al IDE que se trata de un proyecto web y debe usar "Web Tools Platform".

En la línea 8 se aplica el plugin jetty, el cual permite desplegar la aplicación web en una instancia embebida de Jetty durante el desarrollo.

Entre las líneas 21 y 26 se indican las dependencias del proyecto web. En este punto sólo se incluye la dependencia de JSTL para que haga parte de la compilación y generación del WAR ya que los contenedores no suelen incluir esta librería, aunque ello debe confirmarse según el contenedor a usar en los ambientes de producción.

Entre las líneas 28 y 30 se indica el puerto en el cual funcionará Jetty (httpPort), el puerto hacia el cual se enviará el comando para detener su ejecución (stopPort) y la contraseña o llave para detener la ejecución (stopKey).

Más información sobre estos parámetros: https://wiki.eclipse.org/Jetty/Howto/Secure_Termination

gradle.properties

version=1.0.0-SNAPSHOT

Servlet

ExampleServlet.java

Simplemente pondrá un objeto Date en la petición procesada e indicará que se muestre la página "example.jsp"

package com.blogspot.nombre_temp.webapp.gradle.jetty.example;

import java.io.IOException;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ExampleServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("currentDate", new Date());
        request.getRequestDispatcher("example.jsp").forward(request, response);
    }
}

Contenido Web

Como se indicó anteriormente, se debe crear manualmente la carpeta "src/main/webapp/" y a su vez dentro de esta se crearán los siguientes archivos:

index.jsp

Será una página sencilla para verificar que la aplicación está funcionando.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" session="false" %>
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title>Webapp Gradle Jetty Example</title>
</head>

<body>
    <h1>
        Hello World
    </h1>
</body>
</html>

Nota: el atributo "session" en la primera línea es opcional y simplemente se deja para ilustrar cómo es posible tener páginas JSP que no creen una sesión HTTP cuando no es necesario, como en este caso.

example.jsp

Mostrará la fecha y hora a partir del objeto Date enviado por "ExampleServlet" usando el formato indicado en el tag JSTL.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" session="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Webapp Gradle Jetty Example</title>
</head>

<body>
    <h1>
        Current Date: <fmt:formatDate value="${currentDate}" pattern="yyyy-MM-dd HH:mm:ss" />
    </h1>
</body>
</html>

WEB-INF/web.xml

Descriptor de Despliegue (Deployment Descriptor) con la configuración web de la aplicación, requerido al no tener soporte para Servlet 3.0.

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>Example</servlet-name>
        <servlet-class>com.blogspot.nombre_temp.webapp.gradle.jetty.example.ExampleServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Example</servlet-name>
        <url-pattern>/example</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Entre las líneas 8 y 16 se está declarando el Servlet "ExampleServlet" y se indica que recibirá peticiones que lleguen a la ruta "/example", por ejemplo http://localhost:8080/webapp-gradle-jetty-example/example.

Entre las líneas 18 y 20 se indica que en caso de recibir una petición en la cual no se indique una página específica ni tampoco esté relacionada con una ruta de un Serlvet, se debe buscar un archivo "index.jsp" y mostrarlo al usuario en caso de que exista.

Nota: quienes usan Eclipse y aplicaron el plugin "eclipse-wtp" debe asegurarse de que la carpeta "src/main/webapp/" sea incluida en "Deployment Assembly", lo cual puede hacerse manualmente haciendo clic derecho en el proyecto y seleccionando "Properties" -> "Deployment Assembly" o actualizando el proyecto Gradle usando "Buildship" o "Gradle Integration for Eclipse". En caso de que aparezcan errores cargando el "Deployment Descriptor" (web.xml), basta con cerrar el proyecto y abrirlo de nuevo en Eclipse.

Figura 1 - Deployment Assembly con la carpeta webapp

Ejecutar la Aplicación

Desde Eclipse

Quienes usan Eclipse y aplicaron el plugin "eclipse-wtp" pueden desplegar la aplicación en un servidor configurado en Eclipse.

En Eclipse para configurar un servidor se debe usar el menú "Window" -> "Preferences" -> "Server" -> "Runtime Environmets".

Figura 2 - Runtime Enviroments en Eclipse

Figura 3 - Seleccionando un servidor

Figura 4 - Adicionando Tomcat a Eclipse

Una vez adicionando el servidor en Eclipse, este queda disponible en la vista "Servers" desde donde se puede hacer clic derecho, seleccionar "Add and Remove" y escoger el proyecto.

Figura 5 - Vista Servers en Eclipse

Figura 6 - Adicionando proyectos en el servidor

Después de adicionar el proyecto al servidor, este puede ejecutarse o depurarse (debug) con las opciones "Run" y "Debug" de la vista "Servers".

Figura 7 - Opciones para ejecutar la aplicación desde Eclipse

Desde Jetty

El plugin Jetty de Gradle no sólo permite ejecutar la aplicación luego de ejecutar todas las tareas de Gradle necesarias, sino que también hace que no se dependa de que cada desarrollador instale y configure un servidor localmente, y simplemente al ejecutar la tarea "jettyRun" se ejecutan todas las tareas de Gradle y se despliega el proyecto en un Jetty embebido.

./gradlew jettyRun
:compileJava
:processResources
:classes
> Building 75% > :jettyRun > Running at http://localhost:8080/webapp-gradle-jetty-example

También se tiene la tarea "jettyRunWar" y la diferencia es que la primera ejecuta el proyecto desde una carpeta descomprimida mientras la segunda lo hace generando un WAR primero.

Una funcionalidad adicional de este plugin es que los cambios que se realicen en los archivos de "src/main/webapp/" se ven reflejados automáticamente, sin necesidad de volver a compilar y relanzar las tareas de Gradle. Este comportamiento puede modificarse con las propiedades "reload" y "scanIntervalSeconds" del plugin.

Accediendo a la Aplicación

Al acceder a la URL en la que se indica se está ejecutando el proyecto (http://localhost:8080/webapp-gradle-jetty-example) puede verse el mensaje de bienvenida que se dejó en "index.jsp" así como también en los headers HTTP de respuesta puede comprobarse que se está usando Jetty 6.

Figura 8 - Página inicial señalando la versión de Jetty

Ingresando a http://localhost:8080/webapp-gradle-jetty-example/example puede verse que también se ejecuta el Servlet que pone el objecto Date como atributo y el JSP que lo muestra con el formato indicado.

Figura 9 - JSP con la hora actual desde el Servlet

Deteniendo la Aplicación

Cuando la aplicación se ejecuta desde un servidor configurado en Eclipse se tienen dos maneras de detener la aplicación:

  1. La opción "Stop the server" en la vista "Servers".
  2. La opción "Terminate" en la vista "Console".

Si la aplicación se ejecuta desde el Jetty del plugin de Gradle se debe ejecutar la tarea "jettyStop", bien sea desde la línea de comandos (o terminal) o desde la vista "Gradle Tasks" que proporcionan las integraciones para Eclipse (en caso de usarlas).

Depurando la Aplicación (Debug)

En caso de usar un servidor configurado en Eclipse, puede usarse la opción "Start the server in debug mode" en la vista "Servers" o hacer clic derecho en el servidor y seleccionar "Debug", como se señala en la figura 7.

Si se está usando el plugin Jetty de Gradle, infortunadamente las tareas "jettyRun" o "jettyRunWar" no tienen la posibilidad de incluir el parámetro "--debug-jvm" que sí tiene el plugin "java", por lo cual es necesario crear la variable de entorno en sistema operativo "GRADLE_OPTS" con el valor "-Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n".

Ello permitirá que al ejecutar "jettyRun" o "jettyRunWar" se pueda configurar la depuración remota, como se indica en la sección "Depurar una aplicación Java (Debug)" del post sobre "Gradle Integration for Eclipse" o sobre "Buildship".

Generando el WAR

Una vez la aplicación esté lista para desplegarse en un Contenedor Web, el archivo WAR se genera ejecutando la tarea de Gradle "build", lo cual creará el archivo en la carpeta "build/libs".

./gradlew build
:compileJava
:processResources
:classes
:war
:assemble
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Por defecto el nombre del archivo tendrá la estructura: nombre-proyecto-version.war, por lo que en este caso sería: webapp-gradle-jetty-example-1.0.0-SNAPSHOT.war.

En caso de requerir un nombre diferente para el archivo generado puede adicionar algo como lo siguiente a "build.gradle":

war {
    archiveName 'example.war'
}

Conclusiones

El plugin Jetty de Gradle permite se puedan desarrollar aplicaciones web, teniendo mayor independencia en cuanto al ambiente de desarrollo ya que no se requiere que cada desarrollador instale y configure un servidor localmente y pueda usar el IDE de preferencia.

Sin embargo es notorio que el plugin no ha sido lo suficientemente evolucionado con el paso del tiempo, prueba de ello es que sólo soporta Jetty 6. Es por ello que en el siguiente post se mostrará como usar el plugin Gretty, el cual no sólo soporta nuevas versiones de Jetty sino también Tomcat y otras funcionalidades adicionales.

Referencias

sábado, 21 de noviembre de 2015

Proyectos Gradle en Eclipse usando Gradle Integration for Eclipse

Introducción

En un post anterior se mostró cómo utilizar Buildship para trabajar con proyectos Gradle en Eclipse. En esta ocasión se mostrará el plugin "Gradle Integration for Eclipse" el cual lleva un poco más de tiempo desarrollado por Pivotal (la empresa que también desarrolla Spring Framework) y puede considerarse más maduro y estable.

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 - Gradle Integration for Eclipse en el 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

Luego se debe indicar el nombre del nuevo proyecto y seleccionar la plantilla de ejemplo (sample) para que el plugin cree la estructura inicial del proyecto. En este caso se seleccionará "Java Quickstart" no solamente por ser el más sencillo, sino porque en últimas será uno de los más usados. Más información sobre las plantillas disponibles se puede encontrar en: https://github.com/spring-projects/eclipse-integration-gradle/wiki/New-gradle-project-wizard

Figura 3 - Nuevo proyecto 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). Ante esta situación la comunidad detrás de "Gradle Integration for Eclipse" recomienda 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

El archivo "build.gradle" que genera "Gradle Integration for Eclipse" tiene algunas diferencias con respecto al generado por "Buildship", pero ambos permiten modificar a gusto (o necesidad) este archivo. Así por ejemplo cada quien es libre de usar Maven Central, JCenter o cualquier otro repositorio de dependencias adicional.

Ejecutando Tareas de Gradle

Vista "Gradle Tasks"

"Gradle Integration for Eclipse" adiciona a Eclipse la vista "Gradle Tasks", la cual puede mostrarse usando el menú "Window" -> "Show View" -> "Other" -> "Gradle" -> "Gradle Tasks", o también desde el "Quick Access" de Eclipse.

Figura 8 - Vista Gradle Tasks desde Quick Access

Figura 9 - Vista Gradle Tasks

Como se ve en la figura 9, en esta vista aparece el listado de las tareas disponibles para ejecutar en el proyecto, igual al resultado de ejecutar la tarea "tasks".

Al hacer doble clic en alguna de las tareas, o seleccionarla y usar la opción "Run a task" de la vista, se ejecutará la tarea y su resultado se mostrará en la vista "Console".

Figura 10 - Tarea de Gradle build ejecutada

Nota: Las advertencias (warnings) que aparecen se deben a que el archivo "build.gradle" indica que se compile usando Java 1.5 (ó 5), pero en esta prueba se tiene Java 8. Pueden ignorarse sin problemas.

Tasks Quick Launcher

Esta opción está disponible en el menú contextual, haciendo clic derecho sobre el proyecto, seleccionando "Gradle" -> "Tasks Quick Launcher".

Figura 11 - Tasks Quick Launcher en el menú contextual

Esta opción permite ingresar el nombre de la tarea de Gradle que se quiera ejecutar e inclusive tiene auto-completado para las tareas y muestra su descripción.

Figura 12 - Seleccionando la tarea en Tasks Quick Launcher

External Tools 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" -> "Gradle Build...". La opción "Gradle Build" ejecuta la más reciente tarea de Gradle.

Figura 13 - Abriendo External Tools Configurations de Gradle

Figura 14 - Creando una nueva configuración

Como se ilustra en la figura 14, esta ventana es muy similar a "Tasks Quick Launcher", con la diferencia de que estas configuraciones quedan almacenadas en Eclipse posibilitando tener tanto como se necesiten (indicando nombres diferentes para cada configuración) y ejecutarlas desde la opción "Run external tools".

Figura 15 - Tarea de Gradle desde Run external tools

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, seleccionar "Gradle" y escoger una de las opciones disponibles, según se necesite: "Refresh Dependencies", "Refresh Source Folders" o "Refresh All".

Figura 16 - Opciones para actualizar el proyecto Gradle

Figura 17 - Dependencias administradas por Gradle

Cabe anotar que a diferencia de Buildship, con el grupo "Project and External Dependencies", en el grupo "Gradle Dependencies" de este plugin se muestran las dependencias en orden alfabético, lo cual facilita la revisión de las mismas.

Importar un Proyecto Gradle

Mediante "Gradle Integration for Eclipse" 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 18 - Importando un proyecto Gradle

Para continuar con la importación se debe seleccionar su ubicación en el sistema de archivos y luego usar la opción "Build Model" para que el plugin verifique que en efecto se trata de un proyecto Gradle.

Figura 19 - Importando un proyecto luego de usar Build Model

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 20 - 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, usando "Tasks Quick Launcher" o desde la opción "External Tools Configurations" de Eclipse.

Figura 21 - 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.

[sts] -----------------------------------------------------
[sts] Starting Gradle build for the following tasks: 
[sts]      run
[sts]      --debug-jvm
[sts] -----------------------------------------------------
: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 22 - Debug Configurations señalando la opción Debug

Figura 23 - Ventana Debug Configurations

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

Conclusiones

En resumen "Gradle Integration for Eclipse" aunque no es tan diferente de Buildship, sí provee una alternativa para quienes prefieran uno u otro al momento de desarrollar en Eclipse usando Gradle, en lugar de tener un panorama poco prometedor donde sólo se tenga una herramienta disponible (y quizás con poco mantenimiento).

Personalmente continuaré usando "Gradle Integration for Eclipse" ya que como lo mencionaba al principio lo encuentro un poco más estable y maduro, sin embargo al ser Buildship el plugin oficial de Gradle para Eclipse vale la pena revisar de vez en cuando los cambios y mejoras que se le adicionen a dicho plugin.

Referencias

https://github.com/spring-projects/eclipse-integration-gradle
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

domingo, 15 de noviembre de 2015

API REST usando Jersey y Jetty Embebido

Introducción

Van casi 10 años desde que Google decidió cambiar Apache Tomcat por Jetty para su App Engine. Mas que un cambio de un contenedor de Servlets por otro fue el cambio de paradigma, justo como lo dice el slogan de Jetty: "Don't deploy your application in Jetty, deploy Jetty in your application!".

Esto quiere decir que en lugar de desplegar una aplicación Web (generalmente un archivo WAR) en un contenedor previamente instalado y configurado, es el contenedor el que se incluye y se configura en la aplicación. Esto permite que la aplicación pueda copiarse en cualquier servidor (un archivo JAR) y simplemente con ejecutarla tener una réplica de la aplicación totalmente funcional, facilitando la escalabilidad horizontal.

Actualmente existen otros contenedores embebidos como Undertow o el propio Apache Tomcat (desde la versión 7 existe la opción Embedded), sin embargo Jetty sigue siendo muy popular y es por eso que en este post se mostrará cómo se puede iniciar un proyecto Java para un API RESTful usando Jersey 2  y Jetty como contenedor embebido.

Dependencias y Configuración Gradle

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

La manera más fácil de tener un proyecto con Jersey y Jetty es usando la librería "jersey-container-jetty-servlet", la cual incluye todas las dependencias necesarias y además ofrece clases adicionales para facilitar la inicialización de la aplicación. Por ejemplo, este sería el código necesario para iniciar la aplicación:

package com.blogspot.nombre_temp.jetty.jersey.example;

import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import org.eclipse.jetty.server.Server;
import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;

public class ExampleStarter {

 public static void main(String[] args) {
  System.out.println("Starting!");

  URI baseUri = UriBuilder.fromUri("http://0.0.0.0/").port(8080).build();

  ResourceConfig config = new ResourceConfig();
  config.packages("com.blogspot.nombre_temp.jetty.jersey.example.resource");

  final Server jettyServer = JettyHttpContainerFactory.createServer(baseUri, config, false);

  try {
   jettyServer.start();
   jettyServer.join();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

Sin embargo esto viene con un precio y es que se pierde algo de control sobre la configuración del servidor, así como de las dependencias que se tienen. Por ejemplo, la versión más reciente de "jersey-container-jetty-servlet" al momento de escribir este post es "2.22.1", pero esta incluye Jetty "9.1.1.v20140108", siendo "9.3.5.v20151012" la más reciente (más de un año de diferencia).

Es por esto que para este ejemplo se tendrán separadas las dependencias de Jetty y Jersey, facilitando un eventual cambio o migración en cualquiera de estas.

gradle.properties
version=1.0.0-SNAPSHOT

jettyVersion=9.3.5.v20151012
jerseyVersion=2.22.1

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

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

sourceCompatibility = 1.8
targetCompatibility = 1.8

mainClassName = 'com.blogspot.nombre_temp.jetty.jersey.example.ExampleStarter'

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

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

repositories {
    jcenter()
}

dependencies {
    compile "org.eclipse.jetty:jetty-server:$jettyVersion"
    compile "org.eclipse.jetty:jetty-servlet:$jettyVersion"

    compile "org.glassfish.jersey.core:jersey-server:$jerseyVersion"
    compile "org.glassfish.jersey.containers:jersey-container-servlet:$jerseyVersion"
    compile "org.glassfish.jersey.media:jersey-media-json-jackson:$jerseyVersion"
}

En este caso la aplicación se compilará en un archivo JAR, como cualquier otra aplicación ejecutable Java por lo cual se incluyen los plugins "java" y "application" que se vieron en un post anterior. El plugin "release" no es necesario, pero se incluye para facilitar el versionamiento desde el principio, puede considerarse una práctica personal si se quiere.

Las dependencias "jetty-server" y "jetty-servlet" son las necesarias para Jetty, mientras que "jersey-server" y "jersey-container-servlet" son las requeridas para un proyecto que haga las veces de servidor usando Jersey (diferentes a las que se usarían en un proyecto cliente).

Adicionalmente se incluye "jersey-media-json-jackson", para que se puedan recibir y responder peticiones usando JSON. En Jersey 1.x era necesario adicionar la propiedad "com.sun.jersey.api.json.POJOMappingFeature", sin embargo en Jersey 2.x sólo se requiere adicionar esta librería.

Iniciando la Aplicación

El punto de inicio de la aplicación será un método "main", el cual tendrá el siguiente código:
package com.blogspot.nombre_temp.jetty.jersey.example;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.servlet.ServletContainer;

public class ExampleStarter {

    public static void main(String[] args) {
        System.out.println("Starting!");

        ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        contextHandler.setContextPath("/");

        Server jettyServer = new Server(8080);
        jettyServer.setHandler(contextHandler);

        ServletHolder jerseyServlet = contextHandler.addServlet(ServletContainer.class, "/*");
        jerseyServlet.setInitOrder(0);
        jerseyServlet.setInitParameter(ServerProperties.PROVIDER_PACKAGES, "com.blogspot.nombre_temp.jetty.jersey.example.resource");

        try {
            jettyServer.start();

            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    try {
                        System.out.println("Stopping!");

                        jettyServer.stop();
                        jettyServer.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

            jettyServer.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

A continuación una pequeña explicación por líneas:
  • Líneas 14-15: Se indica que la aplicación responderá desde la raíz (por ejemplo http://localhost:8080) y no se crearán sesiones HTTP, las cuales no son necesarias para este caso. Cabe anotar que puede dejarse un valor distinto para "ContextPath", por ejemplo "/api" indicaría que la aplicación respondería desde http://localhost:8080/api.
    Nota: un Handler es un componente de Jetty que se encarga de recibir y procesar las peticiones HTTP.
  • Línea 17: El puerto de la aplicación será 8080.
  • Líneas 20-22: Se adiciona el Servlet de Jersey que recibirá las peticiones y las llevará a las clases que se desarrollen en el proyecto (resources). Se indica también que dichas clases estarán en el paquete "com.blogspot.nombre_temp.jetty.jersey.example.resource" para que Jersey sepa en donde buscarlas.
  • Línea 25: Se inicia el servidor.
  • Líneas 27-39: Este fragmento es realmente opcional, pero se incluye para indicar cómo es posible tener un código que se ejecuta cuando se baja el servidor. Dentro de este hilo es posible liberar recursos de manera ordenada, como por ejemplo conexiones a bases de datos u otros servidores.
  • Línea 41: Detiene el hilo principal de la aplicación (main) mientras Jetty esté funcionando. Esto con el fin de evitar que por ejemplo se afecte el ciclo de vida de Jetty si el hilo principal termina de ejecutarse antes de que Jetty inicie por completo.

Un ejemplo de configuración un poco más avanzada puede ser el siguiente:

package com.blogspot.nombre_temp.jetty.jersey.example;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.servlet.ServletContainer;

public class ExampleStarter {

    public static void main(String[] args) {
        System.out.println("Starting!");

        ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        contextHandler.setContextPath("/");

        QueuedThreadPool queuedThreadPool = new QueuedThreadPool(10, 1);
        final Server jettyServer = new Server(queuedThreadPool);

        int acceptors = Runtime.getRuntime().availableProcessors();

        ServerConnector serverConnector = new ServerConnector(jettyServer, acceptors, -1);
        serverConnector.setPort(8080);
        serverConnector.setAcceptQueueSize(10);

        jettyServer.addConnector(serverConnector);
        jettyServer.setHandler(contextHandler);

        ServletHolder jerseyServlet = contextHandler.addServlet(ServletContainer.class, "/*");
        jerseyServlet.setInitOrder(0);
        jerseyServlet.setInitParameter(ServerProperties.PROVIDER_PACKAGES, "com.blogspot.nombre_temp.jetty.jersey.example.resource");

        try {
            jettyServer.start();

            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    try {
                        System.out.println("Stopping!");

                        jettyServer.stop();
                        jettyServer.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

            jettyServer.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  • Líneas 19 y 20: Aquí se indica que Jetty tendrá mínimo un hilo y máximo 10 para procesar peticiones, para evitar generar demasiados hilos o tener muy pocos para atender las peticiones (en un servidor de producción posiblemente se necesitarían más hilos). Por defecto el límite es 200.
  • Líneas 22 y 26: Se usará el mismo número de núcleos de la CPU como hilos "acceptors" en Jetty para recibir las peticiones HTTP. Por defecto Jetty ya intenta estimar este número según los núcleos limitando a 4, pero en servicios con alta demanda puede que se necesite un número mayor, aunque no se recomienda tener más hilos "acceptors" que núcleos. También se indica que se tendrá una cola de 10 posiciones (acceptQueueSize), es decir un máximo de 10 peticiones en espera mientras los hilos "acceptors" están ocupados.


Por lo general las configuraciones por defecto funcionan bien, pero en caso de tener un tráfico muy alto de peticiones (o una limitación en el número de hilos) se recomienda leer la documentación de Jetty con respecto al tema: http://www.eclipse.org/jetty/documentation/current/high-load.html

Resource

Para mantener el ejemplo sencillo sólo se tendrá una clase Resource con un método, el cual indicará que la aplicación se encuentra funcionando correctamente, algo típico cuando se cuenta con una herramienta de monitoreo de aplicaciones, aunque en este caso no se retornará la clásica cadena "OK", sino un JSON con el estado para demostrar que este proyecto ya tiene soporte para JSON.

package com.blogspot.nombre_temp.jetty.jersey.example.model;

public class Status {

    private String value = "OK";

    public String getValue() {
        return value;
    }
}


package com.blogspot.nombre_temp.jetty.jersey.example.resource;

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

import com.blogspot.nombre_temp.jetty.jersey.example.model.Status;

@Path("/health")
@Produces(MediaType.APPLICATION_JSON)
public class HealthResource {

    @GET
    public Status health() {
        return new Status();
    }
}

Ejecutando la Aplicación

Si se está usando un IDE, es posible iniciar la aplicación ejecutando el método "main", sin embargo al detener la aplicación es posible que no se ejecute el código de finalización, ya que los IDE terminan la ejecución del proceso directamente (kill).

Para ver que se imprima el texto "Stopping!" que se puso al finalizar la aplicación se tienen dos alternativas para ejecutar la aplicación:

1) Ejecutar desde la línea de comandos (terminal) la tarea de gradle "run":

./gradlew run
:compileJava
:processResources
:classes
:run
Starting!
2015-11-14 00:58:59.394:INFO::main: Logging initialized @259ms
2015-11-14 00:58:59.493:INFO:oejs.Server:main: jetty-9.3.5.v20151012
2015-11-14 00:59:00.263:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@6f43c82{/,null,AVAILABLE}
2015-11-14 00:59:00.345:INFO:oejs.ServerConnector:main: Started ServerConnector@3e2055d6{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2015-11-14 00:59:00.346:INFO:oejs.Server:main: Started @1215ms

2) Generar los archivos para distribuir la aplicación ejecutando la tarea de gradle "build".

./gradlew build
:compileJava
:processResources UP-TO-DATE
:classes
: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

BUILD SUCCESSFUL

Total time: 7.843 secs

Al terminar, en la carpeta "build/distributions" se tendrán los archivos .zip y .tar con todos los archivos necesarios para distribuir y ejecutar la aplicación. Se puede descomprimir cualquiera de los dos y dentro de la subcarpeta "bin" se tendrán los archivos para ejecutar la aplicación. Al ejecutar el que corresponda al sistema operativo usado se tendrá lo siguiente en consola/terminal:

./jetty-jersey-example
Starting!
2015-11-09 00:04:10.030:INFO::main: Logging initialized @173ms
2015-11-09 00:04:10.087:INFO:oejs.Server:main: jetty-9.3.5.v20151012
2015-11-09 00:04:10.775:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@5340477f{/,null,AVAILABLE}
2015-11-09 00:04:10.861:INFO:oejs.ServerConnector:main: Started ServerConnector@7d9f158f{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2015-11-09 00:04:10.862:INFO:oejs.Server:main: Started @1007ms

Al abrir un navegado Web (o ejecutar un comando que procese peticiones HTTP) e ingresar la URL "http://localhost:8080/health" la respuesta debe ser el texto: {"status":"OK"}


Figura 1 - Aplicación en el navegador

Finalmente, para terminar la ejecución de la aplicación se debe regresar a la consola/terminal y presionar ctrl + c (o cmd + c en Mac) y se podrá ver que se imprime "Stopping!", como se indicó en el ShutdownHook:

Stopping!
2015-11-09 00:09:01.390:INFO:oejs.ServerConnector:Thread-7: Stopped ServerConnector@7d9f158f{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2015-11-09 00:09:01.408:INFO:oejsh.ContextHandler:Thread-7: Stopped o.e.j.s.ServletContextHandler@5340477f{/,null,UNAVAILABLE}

El código completo de la aplicación podrá descargarse desde: https://github.com/guillermo-varela/jetty-jersey-example

Aplicaciones Web

En próximos posts se mostrará también como es posible desarrollar aplicaciones web (JSP y Serlvets) mediante los plugins Jetty y Gretty de Gradle.

Referencias

http://www.eclipse.org/jetty/documentation
https://jersey.java.net

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