Skip to Content

Recently we had a requirement for generating invoice PDFs from a cloud application. We had the entire application deployed in the cloud and did not want to use the Adobe Document Services(ADS) deployed in the  on-premise Netweaver system to generate the PDF forms.

We tried the Adobe service offered in SAP Cloud platform – ‘SAP Forms by Adobe’. Though the official document covers most of the steps, it does not provide an end-to-end solution. This blog will cover all the steps we performed to implement the solution in the trial landscape.

You can use the SAP Forms by Adobe in two different scenarios

  1. From an application running on an ABAP or Java backend
  2. From an application through the SAP Forms by Adobe REST API via HTTP

The use case in question falls under the second scenario. In order to call the Rest API to generate the PDF forms, we need the following:

a. Form Template – PDF Layout

b. Data to be displayed in the form – Invoice data.

Pass these two parameters while calling the Rest API and get the PDF form back. I have used a simple Java application deployed in the SAP Cloud Platform to call the Adobe forms API.

Some steps are well documented here by SAP. So, I will be covering the additional steps in a more detailed manner.

  1. Enable the ‘SAP Forms by Adobe’ in your trial subaccount. Refer link
  2. Assign roles to users and update the destination. Refer link
  3. Before we can start with the Java application, register an oAuth client in the cloud platform subaccount. We need this to authenticate the java application to call the Adobe service. Refer link

Do not forget to note down the Client ID and the secret which was entered in Step 3. This is required to authenticate the Java application.

All the steps required in the SAP Cloud Platform to use the Adobe form services are complete. Now we need to design the layout of the PDF to be generated and prepare the data to be displayed in the PDF.

  1. Form Template

We will use the Adobe LiveCycle Designer to design the form template. For information about how and where to download this tool, along with licensing information, refer this link.

I have designed a very simple form layout for the blog. This tool can be used to design both Interactive and Non-interactive forms. You can refer the steps in the link to design and create data connections for the form template.

After the layout is complete, save the form template as .xdp file. The file will look something like this. This file has all the layout information required to render the PDF, which is used by the Adobe services.

  1. Data to be displayed.

Once you have the form template ready, you can generate the XML data file with sample data. Go to File->Form Properties. Choose Preview.

Click ‘Generate Preview Data’ and enter a location and name for the XML file to be generated.

Click on ‘Generate’. The XML file with some sample data will be generated and placed in the location. The XML file with sample data looks like this.

This is the exact format which needs to be passed to the Adobe service for it to pick up the data and place it in the exact location as designed in the form template.

Now that we have both the files which are required to make the REST API call, let us proceed with the java application.

Note that the application which calls the REST APIs to generate the PDF should create and send both the files to the Adobe services on cloud to get the PDF file as an output.

6. Java application

We perform the following steps in the java application.

  1. Retrieve Authorisation Token – oAuth token from SCP.
  2. Call the REST API using the oAuth token, the form template (.xdp file) and the data (.xml file).
  1. Retrieve oAuth token

Below is the java code I used to get the oAuth token. Make sure that you enter the Client ID and the client secret which was used in Step 3, when the oAuth client was registered.

/**
* Retrieve the auth token from Adobe service
* @return
*/
private String getoauthToken() {
		String token = "";
		String tokenEndpoint = "https://oauthasservices-sXXXXXXXtrial.hanatrial.ondemand.com/oauth2/api/v1/token";
		
		String client_id = <<ClientID from step 3>>;
		String client_secret = <<Client secret from step 3>>;
		
		String grant_type = "client_credentials";
		String scope = "generate-ads-output";

		HttpClient httpClient = HttpClientBuilder.create().build();
		HttpPost httpPost = new HttpPost(tokenEndpoint);

		String base64Credentials = Base64.getEncoder().encodeToString((client_id + ":" + client_secret).getBytes());
       //1. Prepare the request headers
		httpPost.addHeader("Authorization", "Basic " + base64Credentials);
		httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");

		StringEntity input = null;
		try {
			input = new StringEntity("grant_type=" + grant_type + "&scope=" + scope);
			httpPost.setEntity(input);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		//2. Post the request	
		HttpResponse response = null;
		try {
			response = httpClient.execute(httpPost);
		} catch (IOException e) {
			e.printStackTrace();
		}
        //3. Retrieve the token from the response
		try {
			JSONObject tokenjson = new JSONObject(IOUtils.toString(response.getEntity().getContent(), "UTF-8"));
			token = tokenjson.getString("access_token");
		} catch (IOException e) {
			e.printStackTrace();
		} catch (UnsupportedOperationException e) {
			e.printStackTrace();
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return token;
	}

2. Call the service

SAP Adobe forms on Cloud offers a number of APIs. All of them are documented here. I used the API ‘/adsRender/pdf – Render a PDF Form’ to render the PDF form.

As you can see in the API document, before the API is called, the template and the data files must be encoded as the REST API expects them to be sent as encoded strings.

I used the following code to encode the files.

 /**
   * This method is used to encode the input files
   * @param fileName
   * @return
   * @throws IOException
   */
	private String encodeFileToBase64Binary(final String fileName) throws IOException {
		
            String path = this.getClass().getClassLoader().getResource("").getPath();
	    String fullPath = URLDecoder.decode(path, "UTF-8");
		
	    File file = new File(fullPath + fileName);
	    return Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(file));

	}

Now we have everything we need to call the service. Here is the code to call the service. I have documented the code in-line for your understanding.

/**
* This method is used to call the Adobe service
* @return
*/
private String callService(){
	//1. Get the oAuth token
		String token = getoauthToken();
		
	//2. Prepare the request headers
		String url = "https://adsrestapiformsprocessing-sXXXXXXXtrial.hanatrial.ondemand.com/ads.restapi/v1/adsRender/pdf";
		HttpClient httpClient = HttpClientBuilder.create().build();
		HttpPost request = new HttpPost(url);

		request.addHeader("Authorization", "Bearer "+token);
		request.addHeader("Content-Type", "application/json");
		
        //3. Encode the form template file
		 String inputFileName = "adbforms\\Invoice.xdp"; 
		 String encxdp = "";
		 
		try {
			encxdp = encodeFileToBase64Binary(inputFileName);
		} catch (IOException e1) {
			e1.printStackTrace();
		}

	//4. Encode the data xml file 
		inputFileName = "adbforms\\gendata.xml";  
		String encdata = "";
		try {
			encdata = encodeFileToBase64Binary(inputFileName);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
	//5. Prepare the body of the request
        String json = "{  "
        		+ "\"xdpTemplate\": \""+encxdp+"\", "
        		+ "\"xmlData\": \""+encdata+"\"}";
        		
		StringEntity input = null;
		try
		{
			input = new StringEntity(json);
		}catch(UnsupportedEncodingException e)
		{
			e.printStackTrace();
		}
	//6. Call the service and get the result
		request.setEntity(input);
		HttpResponse response = null;
		try
		{
			response = httpClient.execute(request);
		}catch(IOException e)
		{
			e.printStackTrace();
		}
	
	//7. Retrieve the file name and content from the response
		String file = null;
		String fileName = null;
		try {
			JSONObject tokenjson = new JSONObject(IOUtils.toString(response.getEntity().getContent(), "UTF-8"));
			file = tokenjson.getString("fileContent");
			fileName = tokenjson.getString("fileName");
	//8. Decode and write the file.
			writeUsingOutputStream(file, fileName);
			
		} catch (IOException e) {
			e.printStackTrace();
		}catch(UnsupportedOperationException e)
		{
			e.printStackTrace();
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return file;
		}

The rest API call gives the PDF file as an encoded string. I used the following piece of code to decode the string and write it as a PDF file.

/**
 * This method is used to decode and the write the output PDF file.
 * @param data
 * @param fileName
*/
   private  void writeUsingOutputStream(String data, String fileName) {
			
                        fileName = "adbforms/test.pdf";
			byte[] decoded = Base64.getDecoder().decode(data);
			String path = this.getClass().getClassLoader().getResource("").getPath();
			try {
				String fullPath = URLDecoder.decode(path, "UTF-8");
				 File file = new File(fullPath + fileName);
				 FileUtils.writeByteArrayToFile(file, decoded);
			
			} catch (UnsupportedEncodingException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}   
		}

I ran the java application with the data XML file as shown below

Here is the output PDF response from the cloud service.

Ideally, in a real-world invoice application the xdp file will remain the same for all the calls as it holds the layout, but the data file (.xml) needs to be generated at runtime for each invoice before the Adobe API is called.

As always, please feel free to comment if you have any feedback or questions.

To report this post you need to login first.

4 Comments

You must be Logged on to comment or reply to a post.

  1. Michelle Crapo

    Wow – on this one I really think I have an advantage on-premise.  (At least from an ABAP point of view)  It’s fairly easy to use the already created gateway service builder or create one yourself. (SEGW).  We basically create very little code.  There is a lot of the normal changes / moving things around on the form…  And that can easily be done in Adobe Lifecycle.  If you look at this blog you can see just how easy it is.  (It probably can be done on the cloud as well)

    I’m guessing this blog covers one of the many way to do things.   And I imagine after you do it once or twice it will be easier.

    Thank you for an interesting blog.

    (0) 
    1. Sharadha K Post author

      Michelle,

      Thanks.

      The scenario covered in the blog you have mentioned is completely different from the one this blog discusses about. We had the entire application on the cloud and no on-premise gateway servers involved.

      If I am not wrong the referred blog details about how you can use odata to generate the XML template required by the Adobe tool to generate the form. In the cloud scenario which I have discussed we had to generate this XML template using java code. And of course depending on the technology you use to connect to the Adobe services on cloud the XML can be generated in a number of ways. All the Adobe services on cloud requires is the form template and the data XML to generate the PDF form.

      For the scenario which involves ABAP backend, it might be true that it is easier to use the gateway services and the ADS on premise instead of calling the Adobe services on cloud.

       

      -Sharadha

       

       

       

      (1) 
      1. Michelle Crapo

        Yes – I did see that it was in the cloud without a gateway service.  🙂  Sometimes my comments don’t make a lot of sense.

        What I was referring to, is that I think it is much easier to create the PDF on premise.  🙂   Reading this, it makes it one of the times that I’m happy to on-premise.

        (0) 

Leave a Reply