miércoles, 18 de abril de 2018

Aplicación SAPUI5 para ver un PDF: Servicio oData (II)

En el post anterior, nos habíamos comenzado a pelear con la visualización de un PDF, generándolo desde SAP, ya fuese mediante un Smartform, un Adobe Form o la salida de un informe ABAP.

Pero ese PDF en SAP no nos vale para mucho, y para poder mostrarlo en una aplicación SAPUI5 necesitamos exponerlo de alguna forma. ¿Cómo lo vamos a hacer? Pues claro, mediante un servicio oData.

El objetivo final es crear una aplicación de tipo Master-Detail para mostrar un listado de autores en la parte maestra y un PDF con su bibliografía en la parte del detalle. Es ese PDF el que recuperaremos del formulario anterior y exponemos con nuestro servicio.

Así que vamos a tener que realizar los siguientes pasos:

  • Crear un servicio oData con una entidad de tipo Media.
  • Redefinir el método DEFINE de la Model Provider Class (XXX_MPC_EXT) para indicar qué propiedad será la que identifique el tipo de contenido.
  • En la Data Provider Class (XXX_DPC_EXT), implementar el método XXX_GET_ENTITYSET para devolver el listado de autores.
  • Si quisiésemos mostrar todos los datos de un único autor (aparte de un empleado), implementaríamos también el método XXX_GET_ENTITY, pero para este ejemplo esto NO lo vamos a hacer.
  • Implementar el método /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM, que será donde devolveremos el contenido del PDF. ¿Y cuándo será invocado este método? Cuando solicitemos una entidad específica, añadiendo la opción $value.





Crear el proyecto de servicio oData para PDF


Para crear el servicio oData, nos crearemos un proyecto como ya hemos visto en este post.

Esta vez, vamos a crearnos una entidad llamada "Book", con un entityset llamado "Books". Tendrá dos propiedades, ID (propiedad clave) para identificar al autor y NOMBRE para saber el nombre del autor.

Crear la estructura (SE11)


Como siempre, necesitamos una estructura del diccionario de datos (SE11). Pero, en dicha estructura, vamos a añadir un campo adicional, de tipo /IPRO/MIME_TYPE, que usaremos más adelante para definir el tipo de elemento que devolverá la entidad.



´

Crear proyecto (SEGW)


Después, nos creamos el proyecto en la transacción SEGW y le añadimos nuestra entidad Book y la entityset Books. ¡Y qué no se nos olvidé marcar la entityset con la propiedad addressable (que a mí, como siempre, se me había olvidado y he tenido que editar el post :( ... qué memoria la mía ).

Aquí va lo importante: Para que esta entidad pueda devolver el PDF (u otros tipos de contenido), entramos en los atributos de la entidad y marcamos la casilla de Media.

Esa casillita de Media es la que nos va a salvar el día

Definimos las propiedades de la entidad (el ID del autor y su nombre) y, además, una propiedad adicional (que he llamado MimeType), que enlazamos con el campo adicional que definimos en la estructura de datos.


Hala, ya podemos activar nuestro proyecto para que se generen las clases ABAP correspondientes. Esta parte ya nos la conocemos bien.

Pulsar y listo

Toquetear la clase MPC_EXT


Siempre que hemos creado un nuevo servicio oData, sólo nos hemos preocupado por implementar los métodos de la Data Provider Class (XXX_DPC_EXT).

Pero esta vez tenemos que hacer algo especial. Nos vamos a la Model Provider Class (XXX_MPC_EXT) y redefinimos el método DEFINE, que estará sin implementar. Aquí vamos a indicar qué propiedad del servicio oData es la que almacenará el tipo de contenido.

Redefinimos el método

En este método, añadimos las líneas

METHOD define.
  super->define( ).
  DATA: lo_entity TYPE REF TO /iwbep/if_mgw_odata_entity_typ,
               lo_property TYPE REF TO /iwbep/if_mgw_odata_property.

  lo_entity = model->get_entity_type( iv_entity_name = 'NOMBRE_ENTIDAD' ).
  IF lo_entity IS BOUND.
    lo_property = lo_entity->get_property( iv_property_name = 'Nombre_propiedad_tipo_mime" ).
    lo_property->set_as_content_type( ).
  ENDIF.
ENDMETHOD.

No copiéis y peguéis y ya está, que hay un par de campos que tenéis que modificar en vuestro propio código :P

Con eso, ya podemos activar el servicio con la transacción /IWFND/MAINT_SERVICE. Otra cosa que ya sabemos hacer.

Elijo el alias de sitsema y el servicio correspondiente para añadirlo

Y hala, a activarlo

Devolver el listado de autores disponibles


Ahora toca el turno de implementar el código. Como vamos a crear una aplicación de tipo Master-Detail, necesitamos definir el método XXX_GET_ENTITYSET para poder recuperar todos los elementos disponibles del Master. En este ejemplo, sería el listado de escritores. Cuando seleccionemos uno de los escritores, se mostrará su PDF con su bibliografía en el Detail.

Así que redefiniremos, en la Data Provider Class (XXX_DPC_EXT), el método correspondiente. En este ejemplo, BOOKS_GET_ENTITYSET.

En este método codificaremos el listador de autorazos

Y como no es más que un ejemplo cutre... pues hala, meto el código a capón. Lógicamente, lo suyo sería leer los datos de alguna tabla, llamar a algún módulo de funciones u otra clase, lo que sea.

Hay que imaginarse que esto no es así de cutre :P, sino que leemos de la tabla T9_AUTORES_FRIKIS o algo así

Si activamos y probamos a llamar al entityset de esta forma:

/sap/opu/odata/sap/ZJGC_LIBRARY_SRV/Books/?$format=json

El resultado sería el siguiente:

Ya tenemos nuestros autores disponibles para la parte maestra


Si queremos obtener datos adicionales de una entidad específica, también definiremos el  método XXX_GET_ENTITY (en este ejemplo, BOOKS_GET_ENTITY). Pero, en nuestro caso, sólo nos interesa el PDF cuando elijamos una entrada. Así que no redefinimos el método.

Toca el turno de devolver el PDF


Para poder pedirle al servicio oData que nos devuelva un PDF, vamos a tener que añadir una opción a la URL de la solicitud. Esta opción es $value, y la usaremos así:

/sap/opu/odata/sap/ZJGC_LIBRARY_SRV/Books('LEGUIN')/$value

Gracias a ese $value, el Gateway sabe que, en lugar de entrar por BOOKS_GET_ENTITY, tiene que hacerlo por /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM. Este método es común para todas las llamadas de tipo media, así que lo primero que tenemos que hacer es determinar que entidad ha hecho la llamada y después, añadir el código.

En este método tenemos que hacer lo siguiente, para que se produzca la magia de cargar el PDF:

  • Determinar el entityset con el que se ha hecho la llamada (parámetro de entrada IV_ENTITY_SET_NAME).
  • Leer los parámetros clave para elegir la entidad, que están guardados en el parámetro de entrada IT_KEY_TAB.
  • Ejecutar el código que recupera el XSTRING con el contenido. En nuestro caso, es la función que creamos en el post anterior, ZJGC_LIBRARY. Aaaaaah, ahora encaja todo.
  • ¿Y dónde devolvemos ese contenido? En el parámetro de salida ER_STREAM, donde guardaremos el contenido y el tipo de documento (application/pdf).

Todo eso lo haríamos con el siguiente código:


Y con esto, vamos a hacer la prueba para ver el resultado. Abrimos el navegador (lo podemos hacer desde la /IWFND/MAINT_SERVICE) para ejecutar la llamada de ejemplo que teníamos y, si hemos hecho todo bien, ¡recuperamos el PDF!

Pero a lo mejor nos encontramos con un problema con el que no contábamos: El PDF se descarga automáticamente, en lugar de mostrarse incrustado en el navegador web, ¡y eso no nos vas a quedar bonito para cuando hagamos la aplicación SAPUI5. Quizá nos falte por hacer algo más.


Que sí, que si lo abrimos es el PDF que buscamos, pero no nos lo abre en el navegador.



Mostrar el PDF integrado en el navegador


Si el PDF no se muestra integrado en nuestro navegador, porque el servidor web esté configurado para descargarlo, podemos intentar forzarlo mediante los encabezados HTTP.

Para ello no tenemos que hacer nada especialmente complejo, sólo añadir algo de código en el mismo método GET_STREAM. Para ello, podemos añadir encabezados llamando al método SET_HEADER.

El encabezado que nos interesa a nosotros es Content-Disposition, que puede tener dos valores:


  • inline si lo queremos integrar en el navegador.
  • attachment si lo queremos descargar automáticamente.
  • También podemos añadir la opción Filename para indicar cómo se tiene que llamar el fichero que se va a descargar.


Podemos ver las distintas opciones disponibles en la web de desarrollador de Mozilla.

Lo añadiremos de la siguiente forma:

header-name = 'Content-Disposition'.
header-value = 'inline; Filename="Nombre_por_defecto_al_descargar.pdf"'.
set_header( EXPORTING is_header = header ).

A ver si así nos queda bonito

Si ahora lo volvemos a probar, ¡abracadabra, shazam, shirak! !Conseguimos lo que buscábamos!


NOTA: Podemos añadir otros encabezados HTTP, si nos interesa. Por ejemplo, encabezado Cache-Control para controlar la gestión de caché.


Y lo siguiente es...


Ya sólo nos queda un paso, que es integrar el PDF en la aplicación SAPUI5. Eso lo veremos en el siguiente post.

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.