Setup and consume Forms service by Adobe API

SAP Form Service by Adobe is a Adobe Document Services (ADS) based cloud service that manages print and interactive forms. The stored forms and templates can be consumed as Form Service REST API. Third party applications can consume the forms by passing values to the forms and Form Service REST API generates PDF document based on the input. Refer to SAP help document for more information. SAP Forms Service by Adobe REST API | SAP Help Portal

This blog is a step-by-step guide to get started with Forms service by Adobe API. In the end of the blog, we will test the Form service API by calling them from ABAP environment in BTP.

1. Prerequisites

– BTP subaccount with Forms Service by Adobe license

– Adobe LiveCycle Designer

– ABAP Environment in BTP(or you own application)

2. Get Started

Provided that you have entitlement for Forms Service by Adobe  service, go to your BTP subaccount , go to Service Marketplace from left side pane and choose Create from Forms Service by Adobe tile. Choose “default” as the service instance plan. Follow the same step for Forms Service by Adobe API, but choose “standard” as the service instance plan. 

Service instance for Forms Service by Adobe API should be created in your subaccount. Create a service key and this will be used for creating Form Service API destination.

3. Service Destination

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.

NameADS_SRV
TypeHTTP
DescriptionAdobe API destination
URLuri from service key
https://adsrestapi-formsprocessing.cfapps.<your region>.hana.ondemand.com
Proxy typeInternet
AuthenticationOAuth2ClientCredentials
Client IDClient ID from service key
Client SecretClient Secret from service key
Token Service URLURL from service key + /oauth/token

Click Check Connection to see if the setup set is correct. If you will see the green check box, it’s successful(there seems to be a bug that it doesn’t show the whole text).

This destination can be used in your application built in your application in Cloud Found Environment. By specifying the destination alias name in your application, the authentication process is taken care of by destination service hence no need to fill in the OAuth credential in your application. However, this will not work for external applications that are not able to user the destination in BTP subaccount. In that case, the external application must authenticate against Form Service API with the credential from instance service key.

Later in the step, we will use the destination alias ADS_SRV in ABAP application and see how this works.

You can find the complete list of supported URI in Swagger UI.

4. Accessing Forms service Web apps

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 pagehttps://<Your subdomain>-ads.formsprocessing.cfapps..hana.ondemand.com/ui/customer/index.html
Form service template storehttps://<Your subdomain>-ads.formsprocessing.cfapps..hana.ondemand.com/adsrestapi/ui.html

5. Creating a form template in Adobe LiveCycle Designer

Open Adobe LiveCycle Designer in your local machine. Go to Edit->New and choose “Based on a template”. For our exercise, we will use ‘Invoice’ as the sample template. Click next until you 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 pane and right 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 which is used 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 that there is a template stored, we are ready to call Adobe API to consume this template.

6. Calling Forms service API

To test this API, I’m using ABAP Environment in BTP but the the calling platform is of your choice.

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 in an application. Here are few code snippet I found from the Internet. 

https://blog.aspose.com/pdf/convert-base64-string-to-pdf-jpg-png-image-with-java/

https://base64.guru/developers/java/examples/decode-pdf#:~:text=To%20convert%20a%20Base64%20string,array%2C%20not%20a%20string).

The end result of PDF document will look something like this.