API REST en Liferay 7 con JAX-RS

En este tutorial veremos cómo montar encima de Liferay 7 una API REST con JAX-RS y los servicios locales de Liferay.

Índice

1. Entorno

  • Hardware: Portátil MacBook Pro 17' (2.66 Ghz Intel Core I7, 8GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.4
  • Entorno de desarrollo: iTerm2 y IntelliJ IDEA 2017.1.3
  • Liferay 7.0 CE GA 3
  • Java JRE 1.8.0.131

2. Introducción

Una API de tipo REST nos aporta muchas ventajas, como simplicidad, fácil exploración y facilidad de uso para los clientes debido a su estandarización.

Muy a nuestro pesar, Liferay no expone directamente una API REST, pero sí unos Web Services en JSON (localhost:8080/api/jsonws) y otros en XML (http://localhost:8080/api/axis) (SOAP). Liferay nos da la posibilidad de crear nuestros propios Web Services usando blade y JAX-RS (Implementación de Apache CXF).

Si queréis ver el código fuente aquí tenéis el link al repositorio de Github

He aquí las posibilidades con las que contamos según los diferentes tipos de servicios:

  • Liferay JSON Web Services
    • Autorización
    • Autenticación
    • Configuración automática
    • Explorador de endpoints
    • Local
      • Cualquier lenguaje
      • Servicios propios
    • Remoto
  • Liferay Axis Web Services
    • Autorización
    • Autenticación
    • Configuración automática
    • Explorador de endpoints
    • Local
      • Cualquier lenguaje
      • Servicios propios
    • Remoto
  • Liferay Web Services propios
    • Autorización
    • Autenticación
    • Configuración automática
    • Local
      • Cualquier lenguaje
      • Servicios propios
    • Remoto

En cuanto a los JSON Web Services y a los de Axis podemos consumirlos dentro de un portlet o aplicación local. Si quisiésemos exponer algún servicio a clientes remotos tendríamos que crear Web Services propios que hagan llamadas a los Web Services de JSON o XML. La otra posibilidad es crear un Web Service que interactúe directamente con los localService de Liferay.

También tener en cuenta que, si desplegamos un Web Service propio, perderemos la autorización y autenticación que disponemos con los otros Web Services, es decir que por ejemplo tendremos que comprobar que el usuario X tiene permiso o no para acceder al recurso Y.

Referencias servicios:

3. Generación del módulo

Vamos a proceder a generar un Workspace de Liferay, lo que nos dará muchas facilidades a la hora de desplegar, crear y desarrollar módulos OSGi. Si quieres, puedes ver el tutorial sobre la creación de proyectos, módulos y portlets de Liferay 7 con Blade CLI, donde Javier explica más detalladamente Blade CLI y el Liferay Workspace. Comenzamos a crear el módulo.

Iniciamos con blade el Workspace en el directorio que queramos:

Descargamos el bundle de Liferay:

Una vez hecho esto pasamos a crear nuestro módulo. Usaremos una plantilla predefinida para evitarnos el tener que configurarlo todo desde cero. Para ello:

Donde nuestro <nombre_paquete> será com.autentia.liferay.rest y nuestro proyecto <nombre_proyecto> será rest-users.

Blade infiere de este comando que nuestro endpoint a la hora de registrarlo en la instancia de Liferay será /rest-users. Si queremos cambiar este endpoint tendremos que modificar los dos archivos de configuración dentro de la carpeta $LIFERAY_WORKSPACE/modules/rest-users/src/main/resources/configuration. Tanto el archivo com.liferay.portal.remote.cxf.common.configuration.CXFEndpointPublisherConfiguration-cxf como com.liferay.portal.remote.rest.extender.configuration.RestExtenderConfiguration-rest. Y modificar el /contextPath a lo que queramos. En mi caso será /rest

Nota: Solamente deja modificar desde código la configuración la primera vez, así que si queremos volver a cambiar este valor, tendremos que hacerlo desde la interfaz de Liferay. (Control Panel / Configuration / System Settings / Foundation / REST Extender y CXF Endpoints)

Una vez hecho esto pasamos a iniciar Liferay:

Desde otro terminal y en el directorio de nuestro módulo:

Si introducimos el siguiente comando, veremos el nombre de nuestro módulo en el terminal:

Liferay REST SS Con Gogo shell

Como se puede observar, el módulo con id 486 es el módulo que acabamos de desplegar.

Nota blade sh hace que nos conectemos a través de Apache Felix a la Gogo Shell, que es la que nos da toda la información de los módulos y nos permite interactuar con ellos.

Ahora, si accedemos a http://localhost:8080/o/rest/greetings en nuestro navegador, podremos ver la respuesta It works!.

Nota: Por defecto todos los módulos OSGi que creamos colgarán del endpoint o (o de OSGi).

4. Arquitectura API REST

Según la arquitectura REST el enfoque cae en los recursos, que son sustantivos en plural (usuarios, roles, artículos, etc.) y sobre estos recursos, dependiendo del método http que usemos se tomarán unas acciones u otras. En el protocolo HTTP tenemos 4 verbos principales: GET, POST, PUT y DELETE. Con estos verbos podemos hacer operaciones CRUD sobre un recurso.

Haremos las peticiones con una extensión de Chrome llamada Postman para comprobar que estas se realizan correctamente.

5. JAX-RS

JAX-RS (Java API for RESTful Web Services) es una especificación de Java que da soporte a la creación de servicios web basados en la arquitectura Representational State Transfer (REST). Hace uso de anotaciones y tiene varias implementaciones, como: Jersey, Apache CXF, RESTeasy, RESTlet, etc.

Liferay usa la implementación de Apache CXF, aunque al ser una especificación al aprender una aprendemos todas. Actualmente JAX-RS va por la versión 2.0 que fue sacada en 2013.

En nuestra clase RestUsersApplication pondremos el siguiente código:

Ahora nos creamos una clase RestUserResource que es donde estará la implementación con los métodos GET, POST, PUT y DELETE, aunque de momento probaremos con el método GET nada más. Además, aquí haremos las llamadas al servicio de Liferay para recoger los datos. Una posible mejora de este programa sería crear una clase de servicio para nuestro RestUserResource, aunque esto lo dejamos a manos del lector.

Dado que usamos el servicio UserLocalService debemos añadir este a gradle. Con lo que en el archivo build.gradle de nuestro módulo incluimos lo siguiente:

Nota: compileOnly es parte del plugin de gradle de Liferay, e indica que las dependencias serán recogidas en caliente de la instancia de Liferay, ya que tiene esas librerías expuestas por defecto. Si quisiésemos incluir nosotros una librería externa, debemos usar compileInclude, que hace que compile la librería y además añade la entrada al Manifest.mf. Más información aquí.

Un punto importante a tener en cuenta es que los servicios de Liferay devuelven entidades User mientras que nosotros queremos que devuelva una entidad creada por nosotros, ¿por qué es esto?

Esto es debido a que JAX-RS no tiene conocimientos sobre cómo parsear nuestro POJO a JSON o XML, con lo que hay que anotar nuestra entidad con la etiqueta @XmlRootElement.

Aquí tenemos un problema, ya que nosotros no podemos modificar directamente (y no deberíamos) la entidad User del kernel de Liferay, con lo que nos crearemos un POJO intermedio (RestUser) que mapeará aquellos campos que queremos de User a RestUser.

Por lo que cuando hacemos una llamada a userLocalService tenemos que convertir User a RestUser. En el caso de getUsers() uso un Stream de Java 8 para recorrer el ArrayList de User y por cada uno hago una instancia de RestUser y devuelvo por tanto una lista de RestUser.

He aquí el código de nuestro RestUser:

En versiones de JAX-RS anteriores había que crear una clase MessageBodyWriter que se encargaba de parsear nuestra entidad, pero en la nueva versión no hace falta, lo que nos quita mucho boilerplate.

6. GET

En nuestra recién creada clase RestUserResource incluimos el siguiente código:

Hacemos blade deploy y probamos a hacer una petición desde Postman a http://localhost:8080/o/rest/users (cada vez que modifiquemos nuestro módulo haremos un blade deploy).

Liferay API REST - Petición GET Usuarios

Importante añadir en la sección Headers el campo Accept y poner como valor application/json ya que si no nos dará el siguiente error: No message body writer has been found for class java.util.ArrayList, ContentType: text/html (en Postman no dará error ya que Postman todas las peticiones que hace pone por defecto una cabecera de application/json).

Si cambiamos el valor del campo Accept a application/xml, veremos que devuelve la respuesta en formato XML. Esto es gracias a que tenemos anotado nuestro RestUser con XmlRootElement, de no ser así nos tocaría a nosotros programar la funcionalidad de parseo a XML o JSON.

Vamos a implementar un endpoint que devuelva un único usuario por id. Para ello en la clase RestUserResource añadimos el siguiente método:

Nota: La anotación @PathParam recoge de la url el parámetro id. (ej: el 20164 de la url “http://localhost:8080/o/rest/users/20164” y lo mapea a nuestra variable.

Probamos ahora con el Postman:

Liferay API REST - Petición GET Usuario

Sin embargo, si introducimos el id de un usuario que no existe no recibimos un error 404, si no un error 500, lo que indica fallo interno del servidor. Esto es debido a que hacemos un throw en nuestro método.

Hay una forma mejor de manejar las excepciones con JAX-RS y es la siguiente:

Nota: Dado que estamos en OSGi depurar a base de prueba y error no es especialmente óptimo, usamos log para ver por consola los errores.

De esta forma, si hacemos una petición sobre un usuario que no existe, recibiremos un error 404.

Aquí tienes un listado de las excepciones de JAX-RS:

Excepción Código de estado Descripción
BadRequestException 400 Mensaje malformado
NotAuthorizedException 401 Fallo en la autenticación
ForbiddenException 403 No hay suficientes permisos
NotFoundException 404 No se ha podido encontrar el recurso
NotAllowedException 405 Método de HTTP no válido
NotAcceptableException 406 Media Type no válido
NotSupportedException 415 Media Type de POST no válido
InternalServerErrorException 500 Error interno del servidor
ServiceUnavailableException 503 Servidor no disponible

Referencia manejo errores JAX-RS

7. POST

El código para crear un User (ojo cuidado, que tenemos que crear un User y no un RestUser) es el siguiente:

Liferay API REST - addUser

Cosas a tener en cuenta:

  • emailAddress es un campo obligatorio y no puede estar duplicado
  • locale no puede ser nulo
  • birthdayDay empieza en 1, no como birthdayMonth que empieza en 0

Hacemos una petición POST sobre http://localhost:8080/o/rest/users de la siguiente forma:

Liferay API REST - Postman POST User

Por convención una vez se crea o modifica un recurso es conveniente devolver el recurso recién creado o modificado.

8. PUT

Nota: Según el método PUT hemos de pasar el recurso entero en el body con todos los campos, aunque estos no sean los que queramos modificar. Si quieres otra opción puedes mirar el método PATCH.

Liferay API REST - Petición PUT Usuario

9. DELETE

Liferay API REST - Petición DELETE Usuario

10. Conclusión

La inclusión de la arquitectura OSGi en Liferay 7 ha posibilitado la creación de módulos como el que acabamos de crear con mucha facilidad. Además, hemos visto cómo montar una API REST con JAX-RS.

11. Referencias