Tuesday, January 7, 2014

Creating a PDF dynamically from an XPage - Part 3

In this my third post on how to dynamically create a PDF from an Xpage, I am going to explain my use of tools for creating the XSL stylesheet.  Part 1 is here, and Part 2 is here.

Overview

The XSL stylesheet is what is used to layout the design of the data onto the page.  It reads an XML file, and transforms it using the XSL with the result being a PDF document.

In the past, I have used this flowchart that I made.  This post will focus on the stylesheet creation in the big gray box.

You can create your own XSL stylesheets without using a tool, but it is like writing java without an IDE.   There are several tools that I know of to create XSL for you.  Most of these tools do much more that just XSL and that is reflected in the price.  This page links to companies that make these tools.  The prices for these range from $400 to ~$1000 for a single user license.   There is also a inexpensive solution to use which is called FO Designer which is about $60.  We went with FO Designer, with the thinking being that this tools just does what we want without paying for features we wouldn't use.  I will save my commentary of it for the opinions sections at the end.

The FO Designer works this way.  You visually layout on the screen your form, then you push a button to generate the XSL-FO.  I would then copy the generated FO into a stylesheet within Domino Designer.  I would then refresh my page, and test the PDF.  The tool had a way to test PDFs but I needed it to work within Domino, and I didn't consider the tools PDF generation a true test.

FO Designer

The Opinion Section

This section is named such so I can tell you what I really think.  Here are the major obstacles that I faced.

1) FO Designer might have been great in the year 2000, but it is inadequate now.  I regret thinking that I could make it work.  I wish I pushed for one of the modern higher $$$ options.  It doesn't seem to support tables, and vertical lines don't render.  It has no undo, and is very clunky to use.  When you create an element and set the Xpath to "absolute", and then copy and paste that element it resets to "relative" which causes the PDF creation to fail in Domino.

2) PDF creation in Domino is an all or nothing proposition.  If there is any part of the code not exactly 100% perfect then the PDF creation will fail, and you won't know why.  I found myself having to examine the generated XSL in sublime text prior to copying into my application.

The Recommendation Section

If you find yourself needing to create PDF's this way then I recommend the following.

1) Choose one a different tool than FO Designer.  It will cost you or your company more money, but it will save a lot of frustration.  I can't offer a recommendation as I haven't tried the others.

2) Test your work NOT in Domino, but using web based tools like this one http://www.utilities-online.info/foprender/#.UsxyvPRDuSo   This tool will give you a hint of what is wrong, when something is wrong, as opposed to just not working.   Once it looks right here, then you can copy it into Domino and test it there.

3) The XSL will transform a single XML file.  If you have multiple document that need to be combined into a single XML file, I found the following to be a quick and easy way to do it.

  • In your java method that generated the XML, do a lookup and get a handle to the related documents
  • Loop through the related documents, and copy the values you want on the PDF onto the main document
  • Once the items are copied to the main document then run the generateXML() method
  • Don't save the main document with all the new values on it, you want the PDF to only have the latest data each time you create it.
As for me, we are putting this part of the project on hold.  I hope we come back to it, as I have invested a lot of time and effort in getting this far.  In another sense I am relieved to move on.

Monday, November 18, 2013

Really Strange Behavior with the REST Service Control in Xpages

Today I encountered some really strange behavior when trying to add a new column to a view that is used by a REST service.  I couldn't find anything out there on this, so I decided to write a short blog post detailing what happened and how I fixed it.

I was wanting to add a new column to the underlining view that the REST service uses.   The column is going to store raw HTML that I am going to use in an ExtJs RowExpander plugin.   I create the new column and add it my ExtJs and nothing happens.  After figuring out it wasn't my code, I checked the REST service and the column was not there.

I verified everything and even restarted Notes.  To prove that the view was being read, I tried deleting a column and refreshing my browser page and sure enough the column disappeared.  To test the REST service I used the URL to see the raw JSON data.  (This is done by adding '/restData' after the Xpage URL, or whatever you entered for pathInfo).

You can see here that I am reading all columns
Here is where it gets weird.  If I enter any quotes string in the column value then nothing shows up.   If the string is the result of most any formula it works.   Note:  @Text("test123") did not work.  For clarity, the value "$91" is the programmatic name of the column "confirm91".

This doesn't work

Column value contains only text

This works

The column result is the same, but the column is now read.

Obviously the formula is fake, but for whatever reason the REST service now sees it.   Now that the REST service is pulling the column, I can use it in a grid of my choosing.  

Thursday, November 14, 2013

Creating a PDF dynamically from an XPage - Part 2

In my first post on the subject of creating PDF's dynamically from an Xpage, I went over the research that needs to be done prior to starting.   In this post I will first give an overflow of the workflow and then go into two specific problems I encountered while trying to get a working proof of concept.

Like I said in my last post, this process is fairly complicated.  I drew up this chart to show the workflow process.   To see a better workflow chart (not drawn in windows paint) see this one from Stephan Wissel.


Step 1:  User presses button, the code behind the button simply sets a sessionScope variable and the calls an xAgent Xpage.

Step 2:   The xAgent code first gets the XML from the backend data by calling a method of an utility class I wrote.  The method uses the generateXML() method of the 'Document' class.   The method will also gather XML from the line items documents as well, and merge them into a single XML document (I haven't wrote this part yet).

Step 3:  The xAgent calls a Java class which uses Apache FOB to transform XML into PDF.   It passes it the XML String and a URI to the location of the XSL stylesheet.

Step 4:  The transformation happens on the server and then presents the user with a dialog box to open or save the PDF.

Problem #1:   Can't get a Writer while an OutputStream is already in use Exception.  

This Exception is caused whenever you have any type of error in the SSJS of your xAgent.    In my case, I was modifying code written by Paul Calhoun.   I missed that there was an package statement that hadn't been changed.   I went ahead and put a Stack Overflow before figuring out what happened.   On my SO question, Tim Tripcony gave me an excellent answer on why that exception was happening and how to avoid it.   Even if you have no interest in creating PDF's you will be smarter for having read it.

Even after figuring this out, I did several things that caused the same exception.   In this context I would translate the exception to mean "Something is wrong with the code in your xAgent"  The best thing to do here it undo what you just did and figure out why the server is choking on your code.  Consider this a generic exception that can be caused by any number of issues.

Problem #2:  Getting a "WrappedRuntimeException" when trying to pass the XML to the Transformer

This problem really confused me until I turned to Stack Overflow and got a great answer from who else but Stephan Wissel.  Looking back, I should have seen this as it was obvious after the fact.  The Transformer class was expecting a URI String that points to the XML to transform.   I was passing it the actual String XML.   This was what caused the exception.  I had to modify the Transformer class slightly to convert the String XML to a StreamSource object that it was expecting.  (If I was using the same Transformer class for views, I would have changed it prior to passing it, I still should probably do this.)

Next Post

In the next post, I will explain how to created the XSL stylesheet, and my challenges with that.  I will also tell how XPath fits into all this.