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.