SAP BTP, SAP ABAP Environment, SAP ABAP Development

Get started with Forms Service by Adobe REST API in BTP

1. Prerequisites

– BTP subaccount with Forms Service by Adobe license(not available in free-tier yet)

– Adobe LiveCycle Designer

– ABAP Environment in BTP

2. Preparation

Go to BTP subaccount -> Service Marketplace and choose Create from Forms Service by Adobe tile. Choose “default” as the service instance plan. Follow the same step with Forms Service by Adobe API, but choose “standard” as the service instance plan.

On the service instance of Forms Service by Adobe API, create a service key. We’ll use the service key to create Adobe API destination in the next step.

3. Service destination for Forms Service by Adobe API

From left side pane, go to Destination under Connectivity and create a new destination from a blank template. Use OAuth2ClinetCrendentials and enter the client credentials from the service key created from the previous step.

Name ADS_SRV
Type  HTTP 
Description  Adobe API destination
URL  uri from service key
https://adsrestapi-formsprocessing.cfapps.<your region>.hana.ondemand.com 
Proxy type   Internet 
Authentication  OAuth2ClientCredentials
Client ID   Client ID from service key
Client Secret   Client Secret from service key 
Token Service URL   URL from service key + /oauth/token 

Upon checking the connection, you will see the green check box(there seems to be a bug that it doesn’t show the whole text).

By using this destination, we can access the Forms service by Adobe API without having to authenticate the API call.

You can find the complete list of supported URI of this API here.

4. Accessing Forms service UIs

Go to Security->Role connection in the subaccount and create a new role collection. Assign roles “ADSAdmin” and “TemplateStoreAdmin” to the role collection. Assign the role collection to your user.

Now you should be able to access Form service configuration page and Form service template store.

Form service configuration page https://<your subdomain>-ads.formsprocessing.cfapps.<your region>.hana.ondemand.com/ui/customer/index.html 
Form service template store   https://<your subdomain>-ads.formsprocessing.cfapps.<your region>.hana.ondemand.com/adsrestapi/ui.html 

5. Creating a sample template in Adobe LiveCycle Designer

Open Adobe LiveCycle Designer in your PC. Go to Edit->New and choose “Based on a template”. In this blog, we will use Invoice as the sample template. Click next all the way to finish the template generation. For the company name, address and so on, we will use the default value suggested by the Form Assistance.

Next, Go to File -> Form Properties and select Preview -> Generate Preview Data. The generated file contains the data structure of the invoice document in xml format.

Go to Data View tab on the left side and left click and choose “New Data Connection”. Use the xml file downloaded on the previous step. This will create a data structure on the Data View tab. Now we are ready to bind the fields in PDF to this data structure.

Let’s bind the fields. Click on the field Company, for example, and on the right side pane you can find Data Binding field. Choose “Use Data Connection” and choose “Company”. After this, you will able to see red and green arrow on the right side of the field on the Data View tab. This means the field on the PDF is bound to the field in data connection.

I’ve done the data binding for Company, Address, StateProvince, Phone and 1st row of Item fields.(You can choose to bind your fields of choice but in this tutorial we will use these fields)

Save the template as Adobe XML Form (*xdp).

Now go to the Template Store(https://<your subdomain>-ads.formsprocessing.cfapps.<your region>.hana.ondemand.com/adsrestapi/ui.html). Create a Form called “Invoice”. In the form, click the + button to upload a template. You will be prompted to choose .xdp file so upload the one created in Adobe LiveCycle Designer.

Now we are ready to call Adobe API.

6. Testing Forms service API

ABAP Environment in BTP is where we will be testing the API. (This is because this blog is part of my blog series and in the next blog, we will use ABAP to pass the data, trigger API to render PDF.)

Login to ABAP Environment in BTP and create a class zcl_fp_client. This class contains the methods to trigger the Forms service API with different requests. For example, URI “/v1/forms/” will return all the forms created in Template Store. URI “/v1/forms/{ formname }/templates/” will return all the templates under the specified form. In zcl_fp_client, there are two methods. Method “get_template_by_name” will return the specified template information and method reder_pdf will render PDF(base64 code) based on the input data.

CLASS zcl_fp_client DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    DATA:
      mo_http_destination TYPE REF TO if_http_destination,
      mv_client           TYPE REF TO if_web_http_client.
    TYPES :
      BEGIN OF ty_template_body,
        xdp_Template        TYPE xstring,
        template_Name       TYPE c LENGTH 30,
        description         TYPE c LENGTH 280,
        note                TYPE c LENGTH 280,
        locale              TYPE c LENGTH 6,
        language            TYPE c LENGTH 280,
        master_Language     TYPE c LENGTH 280,
        business_Area       TYPE c LENGTH 280,
        business_Department TYPE c LENGTH 280,
      END OF ty_template_body,
      BEGIN OF ty_form_body,
        form_Name   TYPE c LENGTH 30,
        description TYPE c LENGTH 280,
        note        TYPE c LENGTH 30,
      END OF ty_form_body,
      tt_forms     TYPE STANDARD TABLE OF ty_form_body WITH KEY form_Name,
      tt_templates TYPE STANDARD TABLE OF ty_template_body WITH KEY template_Name.
    METHODS:
      constructor
        IMPORTING
          iv_name                  TYPE string
          iv_service_instance_name TYPE string OPTIONAL,
      get_template_by_name
        IMPORTING
                  iv_get_binary      TYPE abap_boolean DEFAULT abap_false
                  iv_form_name       TYPE string
                  iv_template_name   TYPE string
        RETURNING VALUE(rs_template) TYPE ty_template_body,
      reder_pdf
        IMPORTING
                  iv_xml             TYPE string
        RETURNING VALUE(rv_response) TYPE string.
  PROTECTED SECTION.
  PRIVATE SECTION.
    METHODS:
      __get_request
        RETURNING VALUE(ro_request) TYPE REF TO if_web_http_request,
      __json2abap
        IMPORTING
          ir_input_data TYPE data
        CHANGING
          cr_abap_data  TYPE data,
      __execute
        IMPORTING
                  i_method           TYPE if_web_http_client=>method
        RETURNING VALUE(ro_response) TYPE REF TO if_web_http_response.

ENDCLASS.

CLASS zcl_fp_client IMPLEMENTATION.
  METHOD constructor.
    TRY.
        mo_http_destination = cl_http_destination_provider=>create_by_cloud_destination(
          i_service_instance_name = CONV #( iv_service_instance_name )
          i_name                  = iv_name
          i_authn_mode            = if_a4c_cp_service=>service_specific
        ).
        mv_client = cl_web_http_client_manager=>create_by_http_destination( mo_http_destination ).
      CATCH
        cx_web_http_client_error
        cx_http_dest_provider_error.
    ENDTRY.
  ENDMETHOD.

  METHOD __execute.
    TRY.
        ro_response = mv_client->execute( i_method = i_method ).
        DATA(response_body) = ro_response->get_text( ).
        DATA(response_headers) = ro_response->get_header_fields( ).
      CATCH cx_web_message_error.

      CATCH cx_web_http_client_error INTO DATA(lo_http_error).

    ENDTRY.
  ENDMETHOD.

  METHOD __json2abap.
    DATA(lo_input_struct)   = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_data( p_data = ir_input_data ) ).
    DATA(lo_target_struct)  = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_data( p_data = cr_abap_data ) ).


    LOOP AT lo_input_struct->components ASSIGNING FIELD-SYMBOL(<ls_component>).
      IF line_exists( lo_target_struct->components[ name = <ls_component>-name ] ).
        ASSIGN COMPONENT <ls_component>-name OF STRUCTURE ir_input_data TO FIELD-SYMBOL(<field_in_data>).
        ASSIGN COMPONENT <ls_component>-name OF STRUCTURE cr_abap_data TO FIELD-SYMBOL(<field_out_data>).

        IF lo_target_struct->components[ name = <ls_component>-name ]-type_kind = cl_abap_typedescr=>typekind_xstring.
          <field_out_data> = cl_web_http_utility=>decode_x_base64( <field_in_data>->* ).
        ELSE.
          <field_out_data> = <field_in_data>->*.
        ENDIF.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

  METHOD get_template_by_name.

    DATA(lo_request) = __get_request( ).
    lo_request->set_uri_path( |/v1/forms/{ iv_form_name }/templates/{ iv_template_name }| ).
    IF iv_get_binary = abap_true.
      lo_request->set_query( |select=xdpTemplate,templateData| ).
    ELSE.
      lo_request->set_query( |select=templateData| ).
    ENDIF.
    DATA(lo_response) = __execute(
      i_method = if_web_http_client=>get
    ).

    DATA(lv_json_response) = lo_response->get_text( ).
    DATA lr_data TYPE REF TO data.
    lr_data = /ui2/cl_json=>generate(
      json = lv_json_response
      pretty_name = /ui2/cl_json=>pretty_mode-camel_case
    ).

    IF lr_data IS BOUND.
      ASSIGN lr_data->* TO FIELD-SYMBOL(<data>).
      __json2abap(
          EXPORTING
            ir_input_data = <data>
          CHANGING
            cr_abap_data = rs_template
      ).
    ENDIF.

  ENDMETHOD.

  METHOD __get_request.
    ro_request = mv_client->get_http_request( ).
    ro_request->set_header_fields( VALUE #(
      ( name = 'Accept' value = 'application/json, text/plain, */*'  )
      ( name = 'Content-Type' value = 'application/json;charset=utf-8'  )
    ) ).
  ENDMETHOD.

  METHOD reder_pdf.

    TYPES :
      BEGIN OF struct,
        xdp_Template TYPE string,
        xml_Data     TYPE string,
        form_Type    TYPE string,
        form_Locale  TYPE string,
        tagged_Pdf   TYPE string,
        embed_Font   TYPE string,
      END OF struct.

    CONSTANTS: cns_storage_name  TYPE string VALUE 'templateSource=storageName',
               cns_template_name TYPE string VALUE 'Invoice/InvoiceTemplate'.
    DATA lr_data TYPE REF TO data.


    DATA(lo_request) = __get_request( ).
    lo_request->set_query( query =  cns_storage_name ).
    lo_request->set_uri_path( i_uri_path = '/v1/adsRender/pdf' ).


    DATA(ls_body) = VALUE struct(  xdp_Template = cns_template_name
                                   xml_Data = iv_xml
                                   form_Type = 'print'
                                   form_Locale = 'en_US'
                                   tagged_Pdf = '0'
                                   embed_font = '0' ).

    DATA(lv_json) = /ui2/cl_json=>serialize( data = ls_body compress = abap_true
                                             pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).

    lo_request->append_text(
          EXPORTING
            data   = lv_json
        ).

    TRY.
        DATA(lo_response) = __execute(
          i_method = if_web_http_client=>post
        ).

        DATA(lv_json_response) = lo_response->get_text( ).

        FIELD-SYMBOLS:
          <data>                TYPE data,
          <field>               TYPE any,
          <pdf_based64_encoded> TYPE any.

        "lv_json_response has the following structure `{"fileName":"PDFOut.pdf","fileContent":"JVB..."}

        lr_data = /ui2/cl_json=>generate( json = lv_json_response ).

        IF lr_data IS BOUND.
          ASSIGN lr_data->* TO <data>.
          ASSIGN COMPONENT `fileContent` OF STRUCTURE <data> TO <field>.
          IF sy-subrc EQ 0.
            ASSIGN <field>->* TO <pdf_based64_encoded>.
*            rv_response = <pdf_based64_encoded>.
            rv_response = lv_json_response.
          ENDIF.
        ENDIF.

    ENDTRY.


  ENDMETHOD.

ENDCLASS.

Next, we will create the main class to run on. This class will first create a connection from destination created in the third step of this tutorial. The logic is inside the constructor of zcl_fp_client. Then the method get_template_by_name is called. After running this class, the result will return the template information we uploaded in the previous step in variable ls_template.

CLASS ztest_class DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    DATA:
      mo_http_destination TYPE REF TO if_http_destination,
      mv_client           TYPE REF TO if_web_http_client.
    INTERFACES: if_oo_adt_classrun.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS ztest_class IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    TRY.
        "Initialize Template Store Client
        DATA(lo_client) = NEW zcl_fp_client(
          iv_name = 'ADS_SRV'
        ).

        "Get form template data
        DATA(ls_template) = lo_client->get_template_by_name(
          iv_get_binary     = abap_true
          iv_form_name      = 'Invoice'
          iv_template_name  = 'InvoiceTemplate'
        ).
        out->write( ls_template-template_name ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

Now let’s change the code and render PDF. Execute the below code and you will get a response which contains “fileContent” and base64 code that represents the PDF document.

CLASS ztest_class DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    DATA:
      mo_http_destination TYPE REF TO if_http_destination,
      mv_client           TYPE REF TO if_web_http_client.
    INTERFACES: if_oo_adt_classrun.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS ztest_class IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    TRY.
        "Initialize Template Store Client
        DATA(lo_client) = NEW zcl_fp_client(
          iv_name = 'ADS_SRV'
        ).

        "create xml string
        DATA(lv_xml_raw) = |<form1>| &&
                           |<InvoiceNumber>Ego ille</InvoiceNumber>| &&
                           |<InvoiceDate>20040606T101010</InvoiceDate>| &&
                           |<OrderNumber>Si manu vacuas</OrderNumber>| &&
                           |<Terms>Apros tres et quidem</Terms>| &&
                           |<Company>| && 'My company ABCDE' && |</Company>| &&
                           |<Address>| && '1234 Ice cream street' && |</Address>| &&
                           |<StateProvince>| && 'Alaska' && |</StateProvince>| &&
                           |<ZipCode>Am undique</ZipCode>| &&
                           |<Phone>| && '1234567' && |</Phone>| &&
                           |<Fax>Vale</Fax>| &&
                           |<ContactName>Ego ille</ContactName>| &&
                           |<Item>| && 'ICE111' && |</Item>| &&
                           |<Description>| && 'Vanilla ice cream' && |</Description>| &&
                           |<Quantity>| && '1' && |</Quantity>| &&
                           |<UnitPrice>| && '10' && |</UnitPrice>| &&
                           |<Amount>| && '10' && |</Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Item></Item>| && |<Description></Description>| && |<Quantity></Quantity>| && |<UnitPrice></UnitPrice>| && |<Amount></Amount>| &&
                           |<Subtotal></Subtotal>| &&
                           |<StateTaxRate></StateTaxRate>| &&
                           |<StateTax></StateTax>| &&
                           |<FederalTaxRate></FederalTaxRate>| &&
                           |<FederalTaxRate></FederalTaxRate>| &&
                           |<FederalTax></FederalTax>| &&
                           |<ShippingCharge></ShippingCharge>| &&
                           |<GrandTotal></GrandTotal>| &&
                           |<Comments></Comments>| &&
                           |<AmountPaid></AmountPaid>| &&
                           |<DateReceived></DateReceived>| &&
                           |</form1>|.
        DATA(lv_xml) = cl_web_http_utility=>encode_base64( lv_xml_raw ).


        "Render PDF by caling REST API
        data(lv_rendered_pdf) = lo_client->reder_pdf( iv_xml = lv_xml ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

Copy the base64 code. As this is demo tutorial, we can reply on the public website to convert base64 to PDF document. Search on the browser with key word “base64 to PDF” and you get plenty of good websites.

Alternatively, you can use Java to convert it programmatically. Here are few code snippet I found from the Internet. Link1 Link2