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.
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 |
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 page | https://<Your subdomain>-ads.formsprocessing.cfapps..hana.ondemand.com/ui/customer/index.html |
Form service template store | https://<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/
The end result of PDF document will look something like this.