Cambio de tren, mismo conductor.

A todos mis lectores:

Os  informo que a partir de ahora, mis publicaciones las realizaré en otro dominio: alvaromonsalve.com.

Mi planteamiento inicial crece y la plataforma directoandroid.es no cubre todo lo que quiero abarcar.

Espero que sigáis mis entradas con la misma confianza como hasta ahora, el que escribe sigue siendo el mismo pero en un nuevo canal: alvaromonsalve.com.

Anuncios

Los números de 2014

Los duendes de las estadísticas de WordPress.com prepararon un informe sobre el año 2014 de este blog.

Aquí hay un extracto:

La sala de conciertos de la Ópera de Sydney contiene 2.700 personas. Este blog ha sido visto cerca de 35.000 veces en 2014. Si fuera un concierto en el Sydney Opera House, se se necesitarían alrededor de 13 presentaciones con entradas agotadas para que todos lo vean.

Haz click para ver el reporte completo.

BitBucket: otro repositorio en la nube

En algún momento, todo programador, fuera del ámbito laboral ,se ha planteado tener sus proyectos en un repositorio, o bien, un conjunto de programadores ha decidido realizar un proyecto en común, o bien, una empresa se ha planteado externalizar su repositorio. Actualmente, con los servicios en la nube, esta tarea es sencilla. Un ejemplo de repositorio es GitHub que seguramente, no tengo los datos comparativos, sea el mas popular.

En esta entrada, voy a presentar un repositorio en la nube distinto de GitHub, su nombre es BitBucket y su enlace a su website es https://bitbucket.org. No pretendo realizar una comparativa, ni decir cuál es la mejor opción ya que, en función de las necesidades de cada individuo o equipo, la elección variará. La página principal tiene el siguiente aspecto:

bitbucket

Para la utilización de este repositorio es necesario darse de alta, es decir, realizar la creación de una cuenta. Sus características, entre otras, son: repositorios privados ilimitados, posibilidad de creación de una wiki, posibilidad de creación de un sistema de control de incidencias, repositorios públicos, repositorios  tipo Git o Mercurial y, sobre todo, tener un repositorio privado de forma gratuita con un límite de hasta cinco personas por repositorio. Como he dicho antes, no pretendo realizar comparativa con otros sistemas.

En mi caso, utilizo como repositorio Git. Si el lector desconoce esta tecnología, le dejo los enlaces que he realizado sobre Git, estos son:

Una vez creada la cuenta, realizaremos la creación del proyecto; para ello, realizaremos click sobre el botón de creación, botón Create. La interfaz gráfica es la siguiente:

crearrepositorio

Como se puede observar en la imagen anterior, el proceso de creación es muy sencillo, simplemente, se introduce el nombre, la descripción, decir si es privado o no, el tipo de repositorio, creación de una wiki y/o de un gestor de incidencias; y, para finalizar, el lenguaje. Una vez creado, la gestión del proyecto se realiza mediante los botones de la parte superior del interfaz.

Llegado a este punto, nos faltaría crear el proyecto en nuestro equipo, crear, en mi caso, el repositorio de Git, codificar el proyecto y realizar la subida tanto al repositorio local y al remoto de BitBucket. La URL de configuración del repositorio sigue el siguiente patrón:

https://[nombreusuario]@bitbucket.org/[nombreusuario]/[nombreproyecto].git

Para la configuración del repositorio remoto en Git tecleamos el siguiente comando:

git remote add origin https://[nombreusuario]@bitbucket.org/[nombreusuario]/[nombreproyecto].git

Una vez configurado, realizaremos los correspondientes cambios de estado y commit. Cuando deseemos realizar la subida a BitBucket ejecutaremos el siguiente comando de Git:

git push -u origin master

Como se puede comprobar, el proceso de creción de un repositorio en BitBucket, así como, la subida de un proyecto es muy sencillo y barato.

Heroku II; Heroku con Eclipse

En la primer entrada, Heroku I: Introducción, creación de un proyecto y despliegue, realicé una introducción y descripción del proceso de creación de una aplicación para Heroku, así como, el despliegue de la aplicación en la nube. Evidentemente, la complejidad del aplicativo era mínima, siendo una aplicación tipo “Hola Mundo”. Todas las operaciones fueron realizadas desde la linea de comando.

En la presente entrada, Heroku II; Heroku con Eclipse, me centraré en la realización de una aplicación utilizando el IDE Eclipse. Como en la entrada anterior, para realizar el despliegue, es necesario conocer Git. Para aquel lector que desconozca dicha tecnología le adjunto los enlaces que he realizado sobre Git, los cuales son:

El primer paso que debemos de realizar es la instalación del plugin de Heroku para Eclipse. Dicho proceso es el típico, añadimos un nuevo repositorio en la opción del menú Help/Install New Software; como url, insertamos lo siguiente: https://eclipse-plugin.herokuapp.com/install. Una vez creado el repositorio, procedemos a instalar el plugin como cualquier otro.

Suponiendo que tenemos el usuario creado de Heroku, la gestión del logueo, del API Key y las claves SSH, se realizan en la opción Heroku de las preferencias. En dicha ventana podemos realizar el logueo, la validación de la API Key y la carga o generación de las claves SSH.

Llegado a este punto, hemos reiniciado el IDE y el plugin está cargado correctamente, es decir, estamos listos para trabajar.

Un detalle importante es recordar que Heroku tiene una limitación de 5 aplicativos por usuario con cuentas libres; entiendo, que en las opciones de pago esta limitación no existirá. Esto es un detalle importante porque si has creado cinco aplicaciones y las has desplegado, cuando quieras crear otra nueva aplicación se mostrará un mensaje de error con las claves SSH.

El proceso de creación es el siguiente:

1.- Creación del proyecto en la opción New/ Other / Heroku / Créate Heroku App from Template. El plugin permite crear un proyecto en función de unas plantillas preconfiguradas, entre otras, existen las siguientes: RESTFul JAX-RS application, Spring MVC & Tomcat application, Embedded jetty-servlet application,… En nuestro caso seleccionaremos la opción de una aplicación RESTFul. Un aspecto del interfaz gráfico es el siguiente:

pantalla_seleccion_plantillas

2.- Una vez  creado el proyecto, el paso anterior realiza la creación del proyecto en el IDE y en Heroku, estamos en disposición para realizar el desarrollo. En mi caso, he realizado la modificación de un servicio para que retorne un texto. El aspecto del proyecto en el IDE es el siguiente:

vista_proyecto_en_ide

Al ser un proyecto con Maven y con Git, las operaciones sobre el proyecto son las típicas para estas tecnologías; por ejemplo, para realizar la subida a Heroku realizaremos: seleccionaremos el proyecto, desplegaremos el menú contextual (botón derecho), seleccionaremos la opción Team y pulsaremos la opción Push to Upstream.

3.- Por último, deberemos realizar la prueba del servicio. Para ello, iremos a un navegador e insertaremos la url del servicio. El aspecto de la salida es el siguiente:

salida_servicio

Los pasos anteriores son cuando todo ha ido bien. Hay ocasiones, al menos mi experiencia, que el proceso de creación se realiza en Heroku pero no en el IDE. En esos casos, la solución radica en importar el proyecto en el IDE (el plugin tiene una opción para la importación); una vez importado, el proceso sería el mismo.

Como se puede apreciar, si se tiene un buen conocimiento de Maven y Git trabajar con Heroku es sencillo e intuitivo, permitiendo la realización de aplicaciones en la nube de una forma rápida. Otro aspecto importante, es la utilización de las base de datos que proporciona Heroku pero ese tema será para otro momento.

Heroku I: Introducción, creación de un proyecto y despliegue

Inicio la primera entrada, de un conjunto de dos post, cuyo tema principal es Heroku. Definimos Heroku como un servicio en la nube, en concreto,  una plataforma como servicio de diferentes lenguajes, como por ejemplo: Java, Ruby y Node.js. En esta entrada me centraré en la creación de un proyecto Java y en su despliegue.

bombillas

El site de Heroku es el siguiente: https://www.heroku.com. Para la utilización de esta plataforma es necesario la creación de una cuenta para poder desplegar las aplicaciones que desarrollemos; dichas aplicaciones, las podremos gestionar desde el panel de control; así como, la selección de la base de datos de la aplicación.

En esta entrada, voy a realizar la descripción de los pasos para la creación de un proyecto Java así como el despliegue en la plataforma. Para la descarga del cliente y la guía oficial se puede acceder mediante el siguiente enlace: https://devcenter.heroku.com/articles/getting-started-with-java

El repositorio que emplea Heroku es Git. Si el lector desconoce este repositorio, le dejo los enlaces que he realizado sobre Git, estos son:

El proyecto ejemplo, es un proyecto Java con servicios REST sobre Jersey. Los pasos a realizar son los siguientes:

1.- Ejecutaremos el comando maven para la generación de arquetipos, en nuestro caso utilizaremos el arquetipo jersey-heroku-webapp:

mvn archetype:generate -DarchetypeArtifactId=jersey-heroku-webapp -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=com.example -DartifactId=simple-heroku-webapp -Dpackage=com.example -DarchetypeVersion=2.8

El resultado de la ejecución del comando es la creación de las carpetas del proyecto así como la creación de los ficheros: Procfile y system.properties. Estos ficheros contienen información de configuración y se localizan en la carpeta raíz del proyecto.

2.- Desde la línea de comandos, realizaremos el logueo el Heroku tecleando el comando heroku login.

3.- Realizaremos la creación del repositorio Git en el proyecto creado; para ello, teclearemos el comando: git init

4.- Creación de la aplicación en la plataforma Heroku mediante el comando: heroku create. El resultado del comando: creación del proyecto en la plataforma y, en la línea de comandos, se muestra los enlaces de la url de la aplicación y el enlace al proyecto git.

5.- Realizaremos la codificación del proyecto.

6.- Cambiaremos el estado de los elementos a subir al repositorio de git; entre otros, se debe de  subir: el fichero pom.xml, Procfile, el fichero system.properties y la carpeta src. Un ejemplo es el siguiente: git add src/ Procfile system.properties

7.- Realizaremos el commit al repositorio mediante el  comando commit, por ejemplo,  el commit inicial sería de la siguiente forma:  git commit -a -m “Commit inicial” . El comando anterior corresponde con el primero de todos. Si no se ha subido todo el código, o bien, no se ha terminado de codificar, repetimos los pasos desde el punto 5.

8.- Una vez que tenemos en nuestro repositorio todo el aplicativo, realizaremos el despliegue en la plataforma. Para ello, ejecutamos el siguiente comando: git push heroku master. En este punto, es posible que tengamos problemas con las claves SSH en la conexión a Heroku. Para solventarlas, deberemos de crear una clave nueva mediante el comando ssh-keygen -y rsa; una vez creada, realizaremos la subida de la clave a Heroku mediante el comando heroku keys:add; para ver las claves subidas, ejecutamos el comando heroku keys. Si se desea profundizar en el tema, el enlace de la documentación es el siguiente.

9.- Para la realización de las pruebas, se accede a la url obtenido del punto 4 con los servicios; por ejemplo: http://damp-castle-9224.herokuapp.com/myresource

En la siguiente entrada, Heroku II; Heroku con Eclipse, realizaré la descripción de la creación de los pasos descritos en esta entrada pero desde Eclipse.

 

RESTful Web Services con Spring

En la presente entrada, RESTful Web Services con Spring, realizaré la descripción de la implementación de servicios REST con Spring con el módulo Spring MVC. Realizaré la definición de unos servicios básicos y un ejemplo de cliente de uno de los servicios.

Para el lector interesado, las publicaciones de webservices que he realizado con otras tecnologías son las siguientes:

Creación del proyecto

La creación del proyecto lo realizaremos con Maven. A partir del arquetipo básico de Java, añadiremos al fichero pom las dependencias de Spring, el mapeador de JSON e insertaremos el servidor embebido Jetty. Además, realizaremos la creación de las carpetas webapp y el fichero web.xml.

El snippet con las dependencias más importantes es el siguiente:

[...]
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-core</artifactId>
 <version>${spring.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-web</artifactId>
 <version>${spring.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-webmvc</artifactId>
 <version>${spring.version}</version>
</dependency>
<dependency>
 <groupId>org.codehaus.jackson</groupId>
 <artifactId>jackson-mapper-asl</artifactId>
 <version>${jackson-mapper-asl.version}</version>
</dependency>
[...]
<plugin>
 <groupId>org.eclipse.jetty</groupId>
 <artifactId>jetty-maven-plugin</artifactId>
 <version>${jetty.version}</version>
</plugin>
[...]

Configuración del proyecto

Spring utiliza el módulo Spring MVC para definir los servicios. Así, en la configuración del contexto, definido en el fichero mvc-dispatcher-servlet.xml, definimos los tag para las anotaciones de Spring MVC y la configuración básica del contexto. El contenido del fichero de contexto de Spring es el siguiente:

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:mvc="http://www.springframework.org/schema/mvc" 
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd
 http://www.springframework.org/schema/mvc
 http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
 <context:annotation-config />
 <context:component-scan base-package="es.directoandroid" />
 <mvc:annotation-driven />
</beans>

Configuración del servidor

La configuración del servidor se realiza en el fichero web.xml. En este caso, el contenido contiene la definición del Servlet de Spring, el parámetro en donde se define la localización del contexto Spring y, para finalizar, el listener del contexto. El contenido del fichero web.xml es el siguiente:

[...]
<display-name>Spring Web MVC Application</display-name>
<servlet>
 <servlet-name>mvc-dispatcher</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
 <servlet-name>mvc-dispatcher</servlet-name>
 <url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
<listener>
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> 
[...]

Servicios REST

Los servicios REST de Spring se definen en clases controladoras, es decir, en clases con la anotación @Controller. Los servicios serán los métodos definidos por la anotación @RequestMapping. La anotación @RequestMapping define el path de acceso al servicio; la anotación @ResponseBody, define el cuerpo de la respuesta del servicio el cual corresponderá con el tipo de objeto que retorna el método;y, por último, la anotación @RequestParam, define el parámetro de la petición al cual se le puede definir un valor por defecto o si es obligatorio o no.

En el ejemplo, defino una clase con tres servicios: el servicio echo, que retorna un texto; el servicio echo2, que retorna una estructura JSON representada por la clase MiBEan;y, el servicio saludo, que retorna una estructura JSON representado por la clase MiBean y con un parámetro. El código de la clase es el siguiente:

@Controller
public class DirectoAndroidController {
private static final String plantilla = "Hola, %s!";
 private final AtomicLong contador = new AtomicLong();
 @RequestMapping("/echo")
 public @ResponseBody
   String echo() {
   return "Hola Mundo!";
 }
 @RequestMapping("/echo2")
 public @ResponseBody
   MiBean echo2() {
   return new MiBean(1L, "Hola Mundo!");
 }
 @RequestMapping("/saludo")
 public @ResponseBody
   MiBean saludo(@RequestParam(value = "name", required = false, defaultValue = "Mundo") String nombre) {
   return new MiBean(contador.incrementAndGet(), String.format(plantilla, nombre));
 }
}

Clientes REST

La definición del cliente REST es muy sencilla en Spring. Definimos una plantilla REST, invocamos al servicio con la URL y, en la invocación, le definimos el tipo de la clase que retorna. La referencia al objeto obtenido es la respuesta con el cual podremos operar con él.

Como se puede ver en la entrada, Android y Spring para Android: consumir webservices REST , el tratamiento en Android es idéntico.

RestTemplate restTemplate = new RestTemplate();
MiBean bean = restTemplate.getForObject("http://localhost:8080/echo2", MiBean.class);
System.out.println("" + bean);

Pruebas

Spring no tiene definido la obtención del fichero wadl de forma automática con lo cual se deberá de crear el componente de creación del fichero wadl. La creación del fichero sale del alcance de esta entrada.

Si se desea el código ejemplo se puede acceder aquí.

Servicios REST con Jersey. Peticiones Asíncronas

En la entrada anterior, Servicios REST con Jersey, realicé una descripción de la definición de servicios REST con Jersey, definición de clientes y cómo probar los servicios con SOAP UI. En esta entrada, Servicios REST con Jersey. Peticiones Asíncronas, me centraré en la definición de servicios asíncronos, así como, los clientes de dichos servicios.

La creación del proyecto y su configuración es exactamente igual que la entrada anterior.

Los servicios que podemos realizar son de tres tipos: un servicio asíncrono básico, un servicio asíncrono con Timeout el cual se puede implementar de dos formas y, por último, un servicio chunked, es decir, un servicio que implica una lectura, un procesamiento y una escritura. En los siguientes apartados realizaremos la definición práctica de un servicio y un cliente por cada tipo.

En las diferentes formas de definir un servicio asíncrono que voy a presentar en los siguientes apartados, la firma de método, es siempre la misma. En todas las formas, se pasa un elemento de la clase AsyncResponse con la anotación @Suspended. Esta anotación, determina qué método es asíncrono y permite inyectar la respuesta del resultado del servicio.

Tarea asíncrona básica

La tarea asíncrona define un servicio que realiza una operación pesada y, cuando finaliza, retorna la respuesta. En nuestro caso, para las pruebas, la operación pesada consiste en esperar 10 segundos. El servicio responde con un literal con el texto “10 segundos”. El cliente realiza una petición asíncrona con un objeto de la clase AsyncInvoker.

El snippet del servicio con la tarea asíncrona básica es la siguiente:

 @GET
 @Path("/ejem1")
 @Produces(MediaType.TEXT_PLAIN)
 public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
   new Thread(new Runnable() {
       @Override
       public void run() {
         String result = operacionPesada();
         asyncResponse.resume(result);
       }
       private String operacionPesada() {
         try {
           Thread.sleep(10000);
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
         return "10 segundos";
       }
   }).start();
 }

El snippet del cliente con la tarea asíncrona es el siguiente:

 private void tareaAsincronaBasicaForma1() {
   Client client = ClientBuilder.newClient();
   AsyncInvoker async = client.target(HTTP_LOCALHOST_8080_WEBAPI + "/asincronos/ejem1").request().async();
   Future<Response> future = async.get();
   Response response = null;
   try {
     response = future.get();
   } catch (InterruptedException e) {
      e.printStackTrace();
   } catch (ExecutionException e) {
      e.printStackTrace();
   }
   System.out.println("Hemos obtenido respuesta estado->" + response.getStatus());
   System.out.println("Hemos obtenido respuesta respuesta->" + response.readEntity(String.class));
 }

Tarea asíncrona con timeout

El servicio asíncrono con TimeOut tiene la misma estructura que la básica salvo que se define un manejador de TimeOut. Este manejador se define en la respuesta asíncrona que se define como parámetro del servicio. El nombre del manejador es TimeoutHandler el cual define un método handlerTimeout. Una vez que se define el manejador y se define el tiempo, en nuestro caso en segundos, se define la tarea y se inicia el hilo. De la misma forma que el caso anterior, se define un método cuya funcionalidad tiene un coste alto en tiempo de ejecución. El código del cliente no tiene cambios significativos.

El snippet del servicio con la tarea asíncrona con timeout es el siguiente:

 @GET
 @Path("/ejem2")
 @Produces(MediaType.TEXT_PLAIN)
 public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
    asyncResponse.setTimeoutHandler(new TimeoutHandler() {
      @Override
      public void handleTimeout(AsyncResponse asyncResponse) {
       asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Excedió el tiempo de ejecución.")
      .build());
    }});
    asyncResponse.setTimeout(20, TimeUnit.SECONDS);
      new Thread(new Runnable() {
          @Override
          public void run() {
             String result = operacionPesada();
             asyncResponse.resume(result);
          }
          private String operacionPesada() {
            try {
              Thread.sleep(21000);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            return "10 segundos";
         }
     }).start();
 }

El snippet del cliente con la tarea asíncrona con timeout es el siguiente:

private void tareaAsincronaTimeOutForma1() {
 Client client = ClientBuilder.newClient();
 AsyncInvoker async = client.target(HTTP_LOCALHOST_8080_WEBAPI + "/asincronos/ejem2").request().async();
 Future<Response> future = async.get();
 Response response = null;
 try {
    response = future.get();
 } catch (InterruptedException e) {
    e.printStackTrace();
 } catch (ExecutionException e) {
    e.printStackTrace();
 }
 System.out.println("Hemos obtenido respuesta estado->" + response.getStatus());
 System.out.println("Hemos obtenido respuesta respuesta->" + response.readEntity(String.class));
}

Tarea asíncrona con timeout e implementando CompletionCallback

La implementación del interfaz CompletionCallback permite definir un manejador; cuando la tarea termina, se envía una respuesta al cliente; el cliente, define un componente que implementa el interfaz InvocationCallback el cual le permite controlar cuando se ha completado la operación, o bien, cuando se ha producido una excepción.

El snippet del servicio con la tarea asíncrona con timeout y CompletionCallback es el siguiente:

 @GET
 @Path("/ejem3")
 @Produces(MediaType.TEXT_PLAIN)
 public void asyncGetWithTimeoutAndCompletionCallback(@Suspended final AsyncResponse asyncResponse) {
   asyncResponse.register(new CompletionCallback() {
     @Override
     public void onComplete(Throwable throwable) {
        if (throwable == null) {
          numberOfSuccessResponses++;
        } else {
         numberOfFailures++;
         lastException = throwable;
        }
     }
 });
 new Thread(new Runnable() {
   @Override
   public void run() {
     String result;
     result = operacionPesada();
     asyncResponse.resume(result);
   }
   private String operacionPesada() {
      try {
        Thread.sleep(5000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return "5 segundos en ejecución.";
    }
   }).start();
 }

El snippet del cliente con la tarea asíncrona con timeout y CompletionCallback es el siguiente:

private void tareaAsincronaCompletionCallback() {
 Client client = ClientBuilder.newClient();
 AsyncInvoker async = client.target(HTTP_LOCALHOST_8080_WEBAPI + "/asincronos/ejem3").request().async();
 Future<Response> future = async.get(new InvocationCallback<Response>() {
   @Override
   public void completed(Response response) {
      System.out.println("Estado respuesta=" + response.getStatus());
      System.out.println("Respuesta=" + response.readEntity(String.class));
   }
   @Override
   public void failed(Throwable throwable) {
      throwable.printStackTrace();
   }
  });
}

Tarea asíncrona con chunked

El servicio chunked es aquel servicio cuya tarea realiza una operación de lectura, un procesamiento y “una escritura”. La salida del servicio difiere del resto ya que retorna un objeto de la clase ChunkedOutput.

El snippet del servicio con la tarea asíncrona con chunked es el siguiente:

 @GET
 @Path("/ejem4")
 @Produces(MediaType.TEXT_PLAIN)
 public ChunkedOutput<String> getChunkedResponse() {
    final ChunkedOutput<String> output = new ChunkedOutput<String>(String.class);
    new Thread() {
       public void run() {
         try {
           String chunk;
           while ((chunk = proximoString()) != null) {
             output.write(chunk);
           }
         } catch (IOException e) {
             e.printStackTrace();
        } finally {
          try {
             output.close();
          } catch (IOException e) {
             e.printStackTrace();
          }
       }
     }}.start();
    return output;
 }
 private String proximoString() {
    String resultado = "";
    try {
      new Thread().sleep(2000);
      resultado = " String_" + new Random().nextInt();
      if(this.numeroDeChunk == 3){
        resultado = null;
      }
      this.numeroDeChunk++;
    } catch (InterruptedException e) {
       e.printStackTrace();
    }
    return resultado;
 }

El snippet del cliente con la tarea asíncrona con chunked es el siguiente:

private void tareaAsincronaChunked() {
  System.out.println("\nIniciamos el cliente tareaAsincronaChunked");
  Client client = ClientBuilder.newClient();
  final Response response = client.target(HTTP_LOCALHOST_8080_WEBAPI + "/asincronos/ejem4").request().get();
  final ChunkedInput<String> chunkedInput = response.readEntity( new GenericType<ChunkedInput<String>>(){} );
  String chunk = "";
  while( ( chunk=chunkedInput.read() ) != null ){
     System.out.println("Chunk recibido="+chunk);
  }
  System.out.println("Finalizamos tareaAsincronaChunked...");
}

Si se desea el código ejemplo se puede acceder aquí.

Para mayor comprensión del ejercicio, he dejado en el código las trazas de la consola.