During the last 2 years absence from my blog I have been very busy working on a large project for delivering a common electronic forms solution. Throughout this time I have gathered extensive knowledge in extending Autonomy’s Liquid Office which is the underlying software that powers this eForms solution. Given the lack of technical articles and blogs related to this product I have decided to create a series of blog articles.
In my first article of this series I will be explaining how to allow an end user to download a PDF copy of the form being filled using a different form template. This may come handy in various ways such as but not limited to:
- Printing a PDF copy without the submit buttons or containing a subset of the form fields
- Downloading a PDF copy to be digitally signed by the end user using an X.509 certificate and then uploaded as a form attachment (maybe this would be an interesting topic to expand in a future blog post).
Steps to download a PDF copy of the form being filled
The following sequence diagram shows the high level steps involved in generating and downloading a PDF copy using a different form template from the one being used.
![]() |
![]() |
![]() |
| 1. The client presses the ‘Download PDF’ button which triggers a JavaScript which triggers the LiquidOffice AJAX call. | 2. The LiquidOffice server receives the HTTP request triggered by the AJAX call and executes the CSForm_OnValidateLookupserver-side function.
3. The OnValidateLookup calls the PDFHelper.generatePDF method to generate a PDF based on the specified settings and returns the URL to download the PDF as the HTTP response to the AJAX call. |
|
| 4. The client receives the server response containing the URL to download the PDF.
5. The client makes an HTTP request to download the PDF. 7. The client receives the PDF document and chooses to either open or save the file. |
6. The custom Java Servlet receives the request to download the PDF and streams the PDF to the client. |
Form client side code
When the user presses the PDFDownload button the btnPDFDownload_OnButtonClick() function is called. This will invoke the pdfDownload function which triggers the form to perform an AJAX call to the server using the btnPDFDownload as its target. If successful the AJAX call returns with the URL to download the PDF copy, otherwise it either returns with a status message or an exception which you will need to modify to handle the behaviour according to your needs.
function btnPDFDownload_OnButtonClick() {
pdfDownload(CSEvent.getTarget().getName());
}
// Downloads a PDF copy
function pdfDownload(target) {
// Trigger a LiquidOffice OnValidateLookup AJAX call to generate the PDF
ajaxCall(target);
// Get the server side response URL
var url = CSForm.getResponseURL();
// Make sure that the return URL is not empty or null
if (url !== undefined && url !== "") {
// Navigate to the PDF Download URL
window.open(url);
}
};
// Triggers a LiquidOffice OnValidateLookup AJAX using the specified target
function ajaxCall(target) {
try {
// Get the URL to submit the AJAX request
var urlstr = CSExt.getFldVal("DFS__SubmitURL");
// Set the LiquidOffice Action and Field values
CSExt.setFldVal("DFS__Action", "DBCommandsOnTabOut");
CSExt.setFldVal("DFS__Field", target);
// Execute the AJAX call
if (cs_ajax_submit(urlstr, "") === 0) {
// Check whether the response contains a status message
if (CSExt.getFldVal("DFS__StatusMsg") !== "") {
// Display the status message to the client
CSClient.alert(CSExt.getFldVal("DFS__StatusMsg"));
}
}
} catch (err) {
trace("ajaxCall() Exception");
trace(err.message);
}
};
function trace(s) {
try {
// Log message to console
console.log(s);
} catch (e) { }
}
Form server side code
The AJAX call triggers the CSForm_OnValidateLookup() server side function. This function verifies that the target set was in fact the btnPDFDownload and calls the getPDFDownloadURL() function which, based on the specified settings, will return the URL to download the generate PDF. The getPDFDownloadURL() function has 2 hardcoded variables called formName and formPath which you will need to change to point to the required PDF Template.
importPackage(Packages.java.io);
importPackage(Packages.com.wordpress.alanattard.liquidoffice);
function CSForm_OnValidateLookup() {
try {
var target = CSEvent.getTarget();
if (CSEvent.getAction() != "Submit" && target != null) {
// Make sure that the target that triggered
// this AJAX call was the PDF download button
if (target.getName().indexOf("btnPDFDownload") == 0) {
// Get the URL for the generated PDF
getPDFDownloadURL();
}
}
}
catch (e) {
// Display the message to the user
CSForm.setStatusMsg(e.message);
}
}
// Gets the URL of a PDF generated and sets it in the ResponseURL hidden field
function getPDFDownloadURL() {
var url, formName, formPath, param, pdfPath;
// The formName and formPath for the form template to use to generate the PDF
// ***** These values must be changed according to the publishing path *****
formName = "PDFTemplate";
formPath = "Forms";
// Call the generatePDF method to generate the PDF based on the provided settings
pdfPath = PDFHelper.generatePDF(CSForm, CSDoc, formName, formPath);
// Check that the generatePDF method returned the PDF filename
if (pdfPath !== "") {
// Construct the QueryString to download the required PDF file
param = "fileName=" + pdfPath + "&formName=" + formName + "&type=save";
// Construct the full URL to the PDF download servlet
url = "/xmlserver/PDFDownload?" + param;
// Instruct the client to navigate to the PDF download URL
CSForm.setResponseURL(url);
}
}
Java PDF Helper Class
The PDFHelper class provides a method called generatePDF which generates a PDF based on the provided parameter values. This method starts by retrieving the FormGUID of the specified PDF Form Template and then uses the FormGUID to get all the form properties. The value of the form document envelope properties DFS__FormGUID, DFS__FormRev and LF__FormID are changed to the corresponding PDF Form Template values. The updated document envelope is used to call the ManagePDF.getFlatPDF method which returns the full path for the generated PDF. The directory information is stripped for security reasons given that the PDF path value will be returned to the end user.
public static String generatePDF(JSForm form, Document doc, String formName, String path) {
String pdfPath = null;
// Get an instance of the Liquid Office logger
Log log = LogFactory.getLog();
try {
// Get the Form GUID for the PDF template
String formGUID = FormManager.getFormGUID("/" + path, formName);
// Get the Form properties
Map <String, String> formMap = FormManager.getFormMap(null, formGUID, null);
// Replace the control details to point to the required PDF template
Envelope.setCommandParamValue(doc, "DFS__FormGUID", formGUID);
Envelope.setCommandParamValue(doc, "DFS__FormRev", (String)formMap.get("DFS__FORMREV"));
Envelope.setCommandParamValue(doc, "LF__FormID", (String)formMap.get("DFS__FORMID"));
// Generate the PDF
pdfPath = ManagePDF.getFlatPDF(doc, null, null, null, false, false, false, true);
if (pdfPath != null) {
// Remove the directory path from the path for security reason
pdfPath = pdfPath.replace(DirectoryManager.getFormsAttachDirAbsolutePath(), "");
}
}
catch (Exception e)
{
// Log the exception in LiquidOffice
log.error(e);
}
return pdfPath;
}
Java Servlet code
The PDFDownload servlet only supports the HTTP GET method but may easily be modified to also support the HTTP POST method. The servlet expects the following request parameters:
- fileName (mandatory)
The filename of the PDF to be download - formName (mandatory)
The file name to be used when downloading the PDF - type (optional)
If ‘save’ the file is streamed as an attachment, otherwise as inline
The servlet will simply rebuild the full path for the PDF and stream the content to the client.
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FileInputStream inp = null;
ServletOutputStream out = null;
BufferedInputStream buf = null;
int readBytes = 0;
// Get an instance of the Liquid Office logger
Log log = LogFactory.getLog();
try
{
// Get the QueryString parameters sent by the caller
String fileName = request.getParameter("fileName");
String formName = request.getParameter("formName");
String type = request.getParameter("type");
// Validated the fileName QueryString
if (fileName == null || fileName.length() == 0) {
// Log the warning in LiquidOffice
log.warn("The PDFDownloadServlet query string 'fileName' must be provided.");
return;
}
// Validated the formName QueryString
if (formName == null || formName.length() == 0) {
// Log the warning in LiquidOffice
log.warn("The PDFDownloadServlet query string 'formName' must be provided.");
return;
}
// Create the full path for the requested PDF document
String pdfFullPath = DirectoryManager.getFormsAttachDirAbsolutePath() + fileName;
// Create a File instance based on the PDF path
File pdfFile = new File(pdfFullPath);
// Validated that the requested PDF exists
if (pdfFile.exists() == false) {
// Log the warning in LiquidOffice
log.warn("The requested PDF file does not exist.");
return;
}
// Construct the download file name
String downloadFileName = formName.replace(" ", "") + ".pdf";
// Set the HTTP Response Content Type
response.setContentType("application/pdf");
// Set the HTTP Response Content Length
response.setContentLength((int) pdfFile.length());
// Set the HTTP Response Content Disposition based on the QueryString 'type' value
if ("save".equalsIgnoreCase(type)) {
response.addHeader("Content-Disposition", "attachment; filename=" + downloadFileName);
} else {
response.addHeader("Content-Disposition", "inline; filename=" + downloadFileName);
}
// Create a FileInputStream for the PDF file
inp = new FileInputStream(pdfFile);
// Create a BufferedInputStream based on the FileInputStream instance
buf = new BufferedInputStream(inp);
// Get an instance of the output stream
out = response.getOutputStream();
// Write the file content to the output stream
while ((readBytes = buf.read()) != -1) {
out.write(readBytes);
}
} catch (Exception e) {
// Log the exception in LiquidOffice
log.error(e);
} finally {
if (out != null) out.close();
if (inp != null) inp.close();
if (buf != null) buf.close();
}
}
How to setup the sample code
The sample code which can be downloaded from here contains 2 form templates and a Java package. The forms need to be deployed in a folder called Forms, otherwise the form server side scripts needs to be modified to reflect the new path. The Java package once compiled needs to be copied to the LiquidOffice SharedResources Process directory under WEB-INF\lib.
I hope that you find this article useful. Please feel free to leave me comment with queries/feedback regarding this topic or suggestions for future articles.



