SAP Cloud Platform, SAPUI5

Merge PDFs using JAVA on SAP Cloud Platform

Recently, I had a requirement to work in “SAP Forms by Adobe” service on SAP Cloud Platform to render PDF using their REST Services. Although I was able to achieve rendering of PDF using Adobe rest services, the service can render only one PDF at once. However, my end-user wanted to key in the document number and view all the PDFs related to that document as one merged PDF ( Types of the form will be selected by the user from UI5 Application ). Since the application was completely built in the cloud on XS HANA and not connected to any SAP ECC backend or SAP S4 System, I could not use the standard ABAP approach to merge the generated PDFs.

I found an alternative approach to this problem and thought of sharing it with the wider SAP community hoping this could be helpful.

Introduction

In this blog post, I am going to show the step-by-step process on how to merge multiple PDFs encoded in base64 format into a single merged PDF on SAP Cloud Platform Neo without connecting to an ABAP Backend system. I will be demonstrating the merge functionality using JAVA Servlets with the help of an open-source library. Finally, we will be exposing the Java Servlet to UI5 application and view the merged PDF.

Pre-Requisite

Following are the pre-requisite required to set up,

  1. Install Eclipse
  2. Add HANA tools on eclipse
  3. Download and configure the required SCP server on Eclipse IDE

Merge PDFs

To merge the PDFs on SAP Cloud Platform, I will be using an open-source library locally imported into Java Servlets. For the demonstration, I will be using the PDFBox library. Apache PDFBox is an open-source pure-Java library that can be used to create, render, print, split, merge, alter, verify and extract text and meta-data of PDF files.

Step – 1: Download the required libraries

As mentioned earlier, I am going to use PDFBox to achieve this functionality, I’ll download the same from their official site. Also, to parse the JSON objects that will be sent as a payload to the Servlet I will be using a simple JSON 1.11 library.

You could download these libraries by a simple Google search. Alternatively, you can add dependencies with the help of a Maven Project as well.

Step-2: Create a new Dynamic Web Project

To create an SAP Cloud-based Java Application, Go-to File > New > Dynamic Web Project

Read More: SAP Cloud Platform Certification Preparation Guide

Provide a project name, this project name will be a part of your URL and Click on Finish. Also, do not forget to add your Server to your Target Runtime.

Once you click on finish, you will have a new Project created with a pre-defined structure. It is not in the scope of this blog post to explain the functionality of each file and folder that is generated. However, you can easily find such information online.

Step-3: Create a Servlet Class

By now, we have made a Java Web Project. The next step would be to create a Servlet Class.

Right-click on Java Resource and then click on New > Servlet

Give Class Name, Package name & click on Finish

Once you click on finish, a java class file is created with pre-defined methods and Servlet configuration.

Make sure you have your Server configured on the project else Java source code will throw an error on Servlet classes.

Step-4: Parse the Base64 encoded PDF format

Since we are going to send the request in a JSON format as a payload of the POST HTTP method from UI5 Application, we will have to parse the body and read the individual PDF Content out of it.

Sample Payload Request:

{
	"pdf":[
		{
			"pdfContent": "JVBERi0xLjYNJeLjz9MNCjk...."
		},
		{
			"pdfContent": "JVBERi0xLjYNJeLjz9MNCjQ...."
		},
                {
			"pdfContent": "JVBERi0xLjYNJeLjz9MNCjQ...."
		}
	]
}

Let’s create a static method in eclipse to read the payload of the POST request received by the Servlet. This method would read the body in the form of Streams, then we build the body back to String with the help of String Builder.

public static String getBody(HttpServletRequest request) throws IOException {

	    String body = null;
	    StringBuilder stringBuilder = new StringBuilder();
	    BufferedReader bufferedReader = null;

	    try {
	        InputStream inputStream = request.getInputStream();
	        if (inputStream != null) {
	            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
	            char[] charBuffer = new char[128];
	            int bytesRead = -1;
	            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
	                stringBuilder.append(charBuffer, 0, bytesRead);
	            }
	        } else {
	            stringBuilder.append("");
	        }
	    } catch (IOException ex) {
	        throw ex;
	    } finally {
	        if (bufferedReader != null) {
	            try {
	                bufferedReader.close();
	            } catch (IOException ex) {
	                throw ex;
	            }
	        }
	    }

	    body = stringBuilder.toString();
	    return body;
	}

Finally, we parse the String body into a JSON Object and get the array of PDF encoded in base64 format.

                String payloadRequest = MergePDF.getBody(request);
		PrintWriter writer=response.getWriter();
		try {		
		//Parse the json string into JSON Object
		JSONParser parser = new JSONParser();
		JSONObject json = (JSONObject) parser.parse(payloadRequest);
		JSONArray aPDF = (JSONArray) json.get("pdf");

Step-5: Merge the PDFs using PDFBox

To Merge the array of PDF into a single PDF, we create another static method in the Servlet. This method would create an instance of the PDFMergerUtility class of the PDFBox library. With the help of this class, set the Destination to an instance of ByteArrayOutputStream.

Post this, loop over the array of PDF objects received in the previous step and add each pdf object with the help of the method addSource.

Finally, merge all the PDF documents with the help of mergeDocument and assign the output to the OutputStream that was set as destination earlier.

public static String _mergePDF(JSONArray aPDF) {
		// TODO Auto-generated method stub
		int n = aPDF.size();
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();		
		try {
		PDFMergerUtility PDFmerger = new PDFMergerUtility();
		PDFmerger.setDestinationStream(outputStream);
		for(int i = 0 ; i < n; i++) {
				JSONObject oPDFObject = (JSONObject)aPDF.get(i);
				String sPDFContent = (String)oPDFObject.get("pdfContent");
				byte[] decodedString = Base64.getDecoder().decode(new String(sPDFContent).getBytes("UTF-8"));
				//PDDocument doc1 = PDDocument.load(new ByteArrayInputStream(decodedString));
				PDFmerger.addSource(new ByteArrayInputStream(decodedString));
			}
			PDFmerger.mergeDocuments();
			byte[] encoded = Base64.getEncoder().encode(outputStream.toByteArray());
			return new String(encoded);
		} catch(Exception e) {
			return e.toString();
		}		
	}

Step-6: Send the Response

Finally, send the response to the requestor in a JSON format.

 HashMap<String, String> oResponseMap = new HashMap<String, String>();
		String sBase64 = MergePDF._mergePDF(aPDF);	
		oResponseMap.put("pdfResponse", sBase64);
		response.setStatus(200);
		response.setContentType("application/json");		 
		writer.print(JSONObject.toJSONString(oResponseMap));

Step-7: Complete Java Source Code

package com.sap.blog;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Base64;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.pdfbox.util.PDFMergerUtility;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

/**
 * Servlet implementation class MergePDF
 */
@WebServlet("/MergePDF")
public class MergePDF extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * Default constructor. 
     */
    public MergePDF() {
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		String payloadRequest = MergePDF.getBody(request);
		PrintWriter writer=response.getWriter();
		try {		
		//Parse the json string into JSON Object
		JSONParser parser = new JSONParser();
		JSONObject json = (JSONObject) parser.parse(payloadRequest);
		JSONArray aPDF = (JSONArray) json.get("pdf");		
		HashMap<String, String> oResponseMap = new HashMap<String, String>();
		String sBase64 = MergePDF._mergePDF(aPDF);	
		oResponseMap.put("pdfResponse", sBase64);
		response.setStatus(200);
		response.setContentType("application/json");		 
		writer.print(JSONObject.toJSONString(oResponseMap));
		} catch(Exception e) {
			response.setStatus(500);
			response.setContentType("text/xml");
			writer.append(e.toString());
		}
	}
	
	public static String _mergePDF(JSONArray aPDF) {
		// TODO Auto-generated method stub
		int n = aPDF.size();
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();		
		try {
		PDFMergerUtility PDFmerger = new PDFMergerUtility();
		PDFmerger.setDestinationStream(outputStream);
		for(int i = 0 ; i < n; i++) {
				JSONObject oPDFObject = (JSONObject)aPDF.get(i);
				String sPDFContent = (String)oPDFObject.get("pdfContent");
				byte[] decodedString = Base64.getDecoder().decode(new String(sPDFContent).getBytes("UTF-8"));
				//PDDocument doc1 = PDDocument.load(new ByteArrayInputStream(decodedString));
				PDFmerger.addSource(new ByteArrayInputStream(decodedString));
			}
			PDFmerger.mergeDocuments();
			byte[] encoded = Base64.getEncoder().encode(outputStream.toByteArray());
			return new String(encoded);
		} catch(Exception e) {
			return e.toString();
		}		
	}

	public static String getBody(HttpServletRequest request) throws IOException {

	    String body = null;
	    StringBuilder stringBuilder = new StringBuilder();
	    BufferedReader bufferedReader = null;

	    try {
	        InputStream inputStream = request.getInputStream();
	        if (inputStream != null) {
	            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
	            char[] charBuffer = new char[128];
	            int bytesRead = -1;
	            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
	                stringBuilder.append(charBuffer, 0, bytesRead);
	            }
	        } else {
	            stringBuilder.append("");
	        }
	    } catch (IOException ex) {
	        throw ex;
	    } finally {
	        if (bufferedReader != null) {
	            try {
	                bufferedReader.close();
	            } catch (IOException ex) {
	                throw ex;
	            }
	        }
	    }

	    body = stringBuilder.toString();
	    return body;
	}

}

You could locally test your Java Application on the Web Server configured earlier. Once we are satisfied with the results, we will go ahead and deploy the application on the SAP Cloud Platform.

Deploy the Java Application on SAP Cloud Platform

Step-1: Export WAR file from Eclipse

Right-click on your project, select Export > WAR file. Post clicking provide the path where the WAR file should be stored on your local machine.

Step-2: Import WAR file on SAP Cloud Platform

Go to your SAP Cloud Platform Tenant, Click on Java Applications and press Import Application.

Navigate to your WAR file from your local machine. Post Importing, a URL will be generated by SCP Application. You can independently test this URL using any third party Web-Application of your choice (e.g.) PostMan.

Once you are satisfied with the results, you can go ahead and integrate this with the UI5 Application.

Consuming Java application in SAPUI5

In this part, we will be creating methods that would merge multiple PDFs on the UI screen and display the results,

To consume Java servlet into SAPUI5 application, Create a SimpleForm with Fileuploader to upload the files. Convert the uploaded files into Base64 format,

Step-1: Convert the Files into Base64 Format

Pass the file object from the front-end view to the controller, subsequently, call the below function to get the file in base4 format. Please note that this function runs asynchronously and uses Promises and is not supported for IE11. Alternatively, one may use Callback to achieve similar functionality.

convertBase64: function (file) {
			var reader = new FileReader();
			return new Promise(function (resolve, reject) {
				reader.readAsDataURL(file);
				reader.onload = function (result) {
					resolve(reader.result.substring(28));
				};
				reader.onerror = function (error) {
					reject(new Error("Error: ", error.message));
				};
			});
		}

Step-1: Call the JAVA API on SAPUI5 Application

Pass PDF content in the Base64 format that is received from the previous function to the function defined below.

This function first prepared the input payload that is to be passed to the Java Application / Servlet, then an AJAX call is been made to get a response back.

_mergePDF: function (pdfContent1, pdfContent2) {
//Create an Input Payload for Java Application to Merge the PDFs 
			var oPDF = {
					pdf: [{
						pdfContent: pdfContent1
					}, {
						pdfContent: pdfContent2
					}]
				},
				sPDF = JSON.stringify(oPDF),
				sUrl = "<Your Destination / URL to the Java Application>";

			$.ajax({
				url: sUrl,
				method:"POST",
				data: sPDF,
				dataType:"json",
				contentType: "application/json;charset=utf-8",
				success: function (data) {
                //Get the meregd PDF response in Base64 format
					var sBaseResult = data.pdfResponse;
                //Display the Base64 merged PDF on the UI5 Screen
					this.displayPDF(sBaseResult);
				}.bind(this),
				error: function (oError) {
					sap.m.MessageBox("Cannot Merge PDF");
				}
			});
		}

Step-2: Display PDF on SAPUI5

The final step would be to load this Base64 PDFContent on to the UI5 application and display the same. Please refer below function for the same,

displayPDF: function (sBase64PDF) {
			var sUrl = this._createUrl(sBase64PDF);
			if (!this.oPDFViewer) {
                //Instantiate the PDFViewer Object if not already done
				this.oPDFViewer = new sap.m.PDFViewer();
			}
			jQuery.sap.addUrlWhitelist("data");
			this.oPDFViewer.setSource(sUrl);
			this.oPDFViewer.open();
		},
		_createUrl: function (sData) {
            //Form the URL from the Base64 content
			return "data:application/pdf;base64," + sData;
		}

Result Time

Run the application, add any two PDF files and see the code run in action.

Step-1: Select PDF files and click on Merge PDF Button

Step-2: View the merged file on the screen

Finally, you get a pop-up with merged PDF rendered inside it,

Leave a Reply

Your email address will not be published. Required fields are marked *