Skip to Content
Technical Articles
Author's profile photo Aocheng Yang

Wrapping Form Service API into your own consumable API

In the previous blog, we went through the steps to get started with Forms service by Adobe REST API. We tested by calling the API from ABAP environment and generated a sample invoice document.

In this blog, we will wrap the logic of calling the Forms service API to render PDF into our own HTTP service. This HTTP service will then be wrapped into custom managed API using API management in BTP. Finally, this customized API is ready to be consumed in SAP Build application, which is described in the next blog.

Blog series

Part1 –  Get ready for Form Service by Adobe API in BTP
Part2 – Wrapping Form Service API into your own consumable API <- This blog
Part3 –  Create No-Code PDF generation app in SAP Build Apps(AppGyver App)

Architecture

1. Prerequisites

– Complete Part 1 of this blog series tutorial.

– API management(one of the capabilities in Integration Suite)

2. ABAP HTTP service

We will start off by extending the ABAP classes we created in the previous blog. Go to ABAP environment and create a new HTTP service. Click on the handler class and the class will be generated for you.

In the new generated class, paste the below code. When the HTTP service is called, method if_http_service_extension~handle_request will be triggered.

CLASS zcl_http_adobe_pdf DEFINITION
  PUBLIC
  CREATE PUBLIC .

  PUBLIC SECTION.

    TYPES:BEGIN OF typ_w_input,
            company     TYPE string,
            address     TYPE string,
            state       TYPE string,
            phone       TYPE string,
            item        TYPE string,
            description TYPE string,
            quantity    TYPE string,
            unit_price  TYPE string,
            amount      TYPE string,
          END OF typ_w_input.

    DATA:gw_input_data TYPE typ_w_input.

    INTERFACES if_http_service_extension .
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_http_adobe_pdf IMPLEMENTATION.


  METHOD if_http_service_extension~handle_request.
    DATA: lo_json TYPE REF TO data,
          lv_rendered_pdf  type string.
    FIELD-SYMBOLS:
      <data>  TYPE data,
      <field> TYPE any,
      <value> TYPE any.

    "Get inbound data
    DATA(lv_request_body) = request->get_text( ).

    "Get each fields from request body(Json)
    lo_json = /ui2/cl_json=>generate( json = lv_request_body ).
    IF lo_json IS BOUND.
      ASSIGN lo_json->* TO <data>.
      ASSIGN COMPONENT `company` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-company = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `address` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-address = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `state` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-state = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `phone` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-phone = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `item` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-item = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `description` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-description = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `quantity` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-quantity = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `unitprice` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-unit_price = <value>.
        UNASSIGN <value>.
      ENDIF.

      ASSIGN COMPONENT `amount` OF STRUCTURE <data> TO <field>.
      IF sy-subrc EQ 0.
        ASSIGN <field>->* TO <value>.
        gw_input_data-amount = <value>.
        UNASSIGN <value>.
      ENDIF.

    ENDIF.


    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>| && gw_input_data-company && |</Company>| &&
                           |<Address>| && gw_input_data-address && |</Address>| &&
                           |<StateProvince>| && gw_input_data-state && |</StateProvince>| &&
                           |<ZipCode>Am undique</ZipCode>| &&
                           |<Phone>| && gw_input_data-phone && |</Phone>| &&
                           |<Fax>Vale</Fax>| &&
                           |<ContactName>Ego ille</ContactName>| &&
                           |<Item>| && gw_input_data-item && |</Item>| &&
                           |<Description>| && gw_input_data-description && |</Description>| &&
                           |<Quantity>| && gw_input_data-quantity && |</Quantity>| &&
                           |<UnitPrice>| && gw_input_data-unit_price && |</UnitPrice>| &&
                           |<Amount>| && gw_input_data-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>| &&
                           |<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
        lv_rendered_pdf = lo_client->reder_pdf( iv_xml = lv_xml ).
    ENDTRY.

    response->set_text(
     EXPORTING
        i_text   = lv_rendered_pdf ).

  ENDMETHOD.
ENDCLASS.

To break down the code, first part of the code fetches the request body in json format and fetch the value for each fields. In TRY, we first create a HTTP client object using zcl_fp_client class created in the blog previously. zcl_fp_client will instantiate Forms service REST API client. Then we store input PDF data into variable lv_xml and call the REST API to render PDF.

Save and activate the class.

3. ABAP Communication Scenario

Next, create a Communication Scenario. From the inbound tab, choose the HTTP service created in the previous step. In the overview tab, use Customer managed communication Scenario Type. Finally, publish it with the button on the right-top corner.

Now go to Dashboard(Fiori launchpad) of ABAP Environment. Create a communication user first from “Maintain Communication Users” tile.

Next, create a communication system from “Communication System” tile. It’s important to enter correct host, which is the URL for ABAP Environment in BTP. You can find the System URL by right clicking your ABAP cloud project-> Properties->ABAP Development.

Note that “https://” is not needed in the Host Name.

Add the communication user as the user of this communication system.

 

Finally, create a Communication Scenario from “Communication Arrangement” tile. Use the communication system that you created. Save the communication scenario and you will get Service URL/Service Interface at the bottom. This URL will be wrapped as REST API in API management in the next section.

 

Create REST API in API Management

*Make sure that you have the following setup in Integration Suite:

– API management capabilities

– API Management Service is configured and running

Create API Provider

Access API Portal and from left side pane, choose Configure(wrench icon). Create a new API Provider by following the below parameters.

 

Type Internet
Host Your ABAP environment System URL
Port 443
Use SSL X
Service Collection URL: /sap/bc/http/sap/zhttp_adobe_pdf
Authentication type Basic
Username Your communication user in ABAP environment
Password Password for the communication user

Test the connection and it should be successful.

 

Add Key Value Map

Go to Key Value Map tab and add the credential of you ABAP communication user and password. This key value map will be used later in API policy management as  basic authentication credential.

Create REST API

Next, go to Develop(pen icon) and create a new API. Fill in the parameters as follows:

Select API Provider
API Provider Your API Provider
URL
/sap/bc/http/sap/zhttp_adobe_pdf
Name xxxxxx
Title xxxxxx
API State Active
Host Alias:
Your defualt Host Alias of your API management service
API Base Path:
/zhttp_adobe_pdf(Or you can decide your own)
Service Type
REST

 

Now your API is created but not deployed yet. Before we deploy it, we have to add request method and policy. Go to Resource tab and add POST method.

And add GET method the same way. Save the progress.

Add policy

Policy allows you to add customized behaviors to your API, in order to control traffic, add/reduce security measures, message transformation, and so on so forth. In this tutorial, we add two behaviors so that it’s easy to consume the API from SAP Build Apps(AppGyver app):

– Allow Cross Origin CORS when consumed from SAP Build Apps:

– Assign Basic authentication from Key Mapping in API management

Handling CORS is a must if you want to call this API as HTTP request from SAP Build Apps(AppGyver app). Adding authentication is an optional. I added authentication so that it’s not required in SAP Build Apps(AppGyver app).

 

Now change to edit mode and choose policy and the screen will take you to policy editor. New policies can be added from the right side pane.

ProxyEndpoint(PreFlow)

Add two policies in the ProxyEndpoint(PreFlow). The first policy will fetch the credential we created in Key Value Map and second policy apply it as basic authentication on the request message.

getCredential(KeyValueMapOperations)

<!-- Key/value pairs can be stored, retrieved, and deleted from named existing maps by configuring this policy by specifying PUT, GET, or DELETE operations -->
<!-- mapIdentifier refers to the name of the key value map -->
<!-- Don't use Key Value Maps to store your logs as this can impact API Proxy runtime flow -->
<KeyValueMapOperations mapIdentifier="ABAP_Environment" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
  <Get assignTo="private.BasicAuthUsername" index='1'>
    <Key><Parameter>username</Parameter></Key>
  </Get>
  <Get assignTo="private.BasicAuthPassword" index='1'>
    <Key><Parameter>password</Parameter></Key>
  </Get>
  <Scope>environment</Scope>
</KeyValueMapOperations>

applyBasicAuth(BasicAuthentication)

Apply the credential fetched in getCredential to the request header.

<BasicAuthentication continueOnError='false' enabled='true' xmlns='http://www.sap.com/apimgmt'>
  <Operation>Encode</Operation>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <User ref='private.BasicAuthUsername'></User>
  <Password ref='private.BasicAuthPassword'></Password>
  <AssignTo createNew="true">request.header.Authorization</AssignTo>
</BasicAuthentication>

ProxyEndpoint(PostFlow)

setCORS(AssignMessage)

Here the Crosses origin is set as allowed in Access-Control-Allow-Origin in response header.

<!-- This policy can be used to create or modify the standard HTTP request and response messages -->
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
    <Add>
        <Headers>
            <Header name="Access-Control-Allow-Origin">*</Header>
            <Header name="Access-Control-Allow-Headers">set-cookie, origin, accept, maxdataserviceversion, x-csrf-token, authorization, dataserviceversion, accept-language, x-http-method, content-type, X-Requested-With</Header>
            <Header name="Access-Control-Max-Age">3628800</Header>
            <Header name="Access-Control-Allow-Methods">GET, PUT, POST, DELETE</Header>
            <Header name="Access-Control-Expose-Headers">set-cookie, x-csrf-token, x-http-method</Header>
        </Headers>
    </Add>
    <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" type="response">response</AssignTo>
</AssignMessage>

TargetEndpoint(PreFlow)

Set the same policy as in ProxyEndpoint(PreFlow) here in TargetEndpoint(PreFlow).

Save the progress and make sure to deploy the API by clicking deploy button. The status of the API should be in green “Deployed”.

Access your API in the browser and you should get the result in json. This is json is returned in the ABAP class we created in step2. In the fileContent is the PDF data in base64 format. You cannot get the physical file at the moment. This base64 code has to be converted like we did in the first blog post. This conversion will be done in the next blog in the AppGyver application.

Conclusion

If you’ve made it this far, congratulations!😃 We have created our own custom API and this is ready to be consumed in SAP Build Apps(AppGyver application).

Follow the next blog(the last one) to create AppGyver application and call this API to generate PDF invoice document.

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Shai Sinai
      Shai Sinai

      Thanks for sharing your detailed solution.

       

      One comment regarding part 2. ABAP HTTP service:

      Wouldn't it be more efficient to convert the data into XML via ABAP simple transformation (or alternatively convert the JSON directly into XML) instead of building manually the XML?

              DATA(lv_xml_raw) = |<form1>| &&
                                 |<InvoiceNumber>Ego ille</InvoiceNumber>| &&
                                 |<InvoiceDate>20040606T101010</InvoiceDate>| &&
                                 |<OrderNumber>Si manu vacuas</OrderNumber>| &&
                                 |<Terms>Apros tres et quidem</Terms>| &&
                                 |<Company>| && gw_input_data-company && |</Company>| &&
                                 |<Address>| && gw_input_data-address && |</Address>| &&
                                 |<StateProvince>| && gw_input_data-state && |</StateProvince>| &&
                                 |<ZipCode>Am undique</ZipCode>| &&
                                 |<Phone>| && gw_input_data-phone && |</Phone>| &&
                                 |<Fax>Vale</Fax>| &&
                                 |<ContactName>Ego ille</ContactName>| &&
                                 |<Item>| && gw_input_data-item && |</Item>| &&
                                 |<Description>| && gw_input_data-description && |</Description>| &&
                                 |<Quantity>| && gw_input_data-quantity && |</Quantity>| &&
                                 |<UnitPrice>| && gw_input_data-unit_price && |</UnitPrice>| &&
                                 |<Amount>| && gw_input_data-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>| &&
                                 |<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>|.
      Author's profile photo Aocheng Yang
      Aocheng Yang
      Blog Post Author

      Hi, you are absolutely right. Hard-coding xml is not a good practice. It should be done dynamically. I was more focused on the making the solution work rather than quality of code.