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