Tuesday, January 30, 2007

Grails -- How to Add Binary PDFs Into a PdfDocument Using iText

We created an application using Grails framework. This application would allow registered users to submit application forms in PDF format. For some special reasons, we have to store all the PDFs into a relational database. Then we need to create a controller, such that the admin user is able to generate a PDF report which includes all the application forms that users had submitted.

The key issue for this task is to retrieve PDF files from the relational database and merge them into a single PDF file. Inspired by the post by Abni, we solved the problem by using iText in the following steps:

Step 1: Create a PdfDocument and PdfWriter:


Document document = new Document(PageSize.A4, 50, 50, 50, 50);
PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream())


Step 2: Retrieve a binary pdf file from database and write into a byte array:

Suppose I have a domain class like this:

class Application {
Long id
ong version

String username //Primary key
byte[] application
...
...
}


In grails, it is easy to retrieve a pdf file out of database and write into a byte array , eg:

def app = Application.findByUsername(userName) //here userName is the primary key
if (app != null && app.application.size() > 0){

def byte[] app_letter = app.application
...
...
}


Step 3: Create a PdfReader from a byte array which represents a PDF file:


PdfReader pdfReader= new PdfReader(app_letter)


Please note that the byte array must represent a valid PDF file, otherwise, an exception is thrown.

Step 4: Create a page from PdfReader and add the page to PdfDocument:

PdfImportedPage applicationPage
PdfContentByte

//the pdf file may contain more than one pages
for (pageNumber in 0..pdfReader.getNumberOfPages()-1){
pageNumber++

applicationPage = writer.getImportedPage(pdfReader, pageNumber) //Create page from PdfReader

cb.addTemplate(applicationPage, 0, 0) // add page to PdfDocument
....
....
}

The snippets above just show how to retrieve one PDF file from database and add it into a PdfDocument. For multiple PDF files, we can do the same thing for each of them and update page number correspondingly.

Discussion:

1. In step 3, the byte array used to create PdfReader must represent a valid PDF file, otherwise, an exception throws. The program should handle the exception.

2. PDF files are all hold in memory. If file number is huge, then we need to consider memory issues.

Reference:

1. Merge PDF files with iText (Abhi on Java)
2. Grails online tutorial
3. Tutorial: iText By Example

Sunday, January 28, 2007

Grails -- How to send out an email after some actions have been done?

In my work, I need to send out a notice to the administrators each time a user submit an application. There are several ways to do this. The example here just demonstrates how easy it can be done by using "afterInterceptor" just in couple of steps:

Step 1: Follow the tutorial posted here to configure a couple of beans which will be used for sending email.

In spring/resources.xml, add the following configurations:


<beanid="mailSender"class="org.springframework.mail.javamail.JavaMailSenderImpl" >
<property name="host">
<value>mailServer.abc.com</value>
</property>
</bean>


Step 2: Copy three jar files into the application' local lib: activations.jar, mail.jar and javaee.jar. These jar files are needed by org.springframework.mail.javamail.JavaMailSenderImpl. which is registered in the above spring/resources.xml file.

Step 3: Add the following code into the controller that I want to intercept. Because I want to attach a PDF file, so I used MimeMessageHelper .


class ApplictionController {

/** the mailSender properties is defined in the spring/resources.xml and are automatically injected by Grails**/
MailSender mailSender

def afterInterceptor = [action:this.&sendMail,only:['save']]

def sendMail= {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

//recipient of email could be a list of email addresses
String [] to = new String[2]
to[0] = "address1@gmail.com"
to[1]= "address2@gmail.com"

helper.setTo(to)
helper.setSubject("It is a test")
helper.setText("check out this PDF file")

//attach a file
FileSystemResource file = new FileSystemResource(new File("/path/to/myApplication.pdf"));
helper.addAttachment("myApplication.pdf", file);

//send mail
mailSender.send(message)
}
...
...
...
}

Commentary:
1. One interceptor can be used to intercept more than one actions, for example:


def afterInterceptor = [action:this.&sendMail,only:['save', 'update']]



The Interceptor will execute each time after "save" or "update" are finished


2. The attachment could be binary data from the database. In this case, read data into a byte array and then create a ByteArrayResource from the byte array. The ByteArrayResource can be attached too.

3. We can intercept before some actions get executed basically the same way but using "beforeInterceptor" instead.

Issues: It turns out a very simple solution once I figured out how to do it. However, the solution above just intercepts actions based on a instance of one controller. What if I want to intercept multiple controller using only one interceptor? Can we do this in Grails?