Thursday, August 4, 2011

Printing example with Java EE

Following  is an approach to printing a document using a servlet. The example uses a template which is in html to generate a page (an invoice) and then prints it. Place holders of variable fields of the invoice are obtained via the user session and replaced appropriately. Since the clients should be getting a printout of the invoice, the printing has to be done through the browser (using a javascript).

===================================================================
package com.shyarmal.web.services;

import com.shyarmal.web.util.DateTimeUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import org.apache.log4j.Logger;

public class InvoicePrinter extends HttpServlet {

    private static final String TEMPLATE_NAME = "/invoice.tpl";
    
    private static final Logger LOGGER = Logger.getLogger(InvoicePrinter.class);
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        LOGGER.info("printing receipt ....");
        try {
            Map receiptDetails = (Map) req.getSession().getAttribute("recieptParameters");
            receiptDetails.put("date", DateTimeUtil.getDate());
            receiptDetails.put("time", DateTimeUtil.getTime());
            PrintWriter pw = resp.getWriter();
            pw.print(replaceTokens(readTemplate(), receiptDetails));
            pw.flush();
            pw.close();
        } catch (Exception e) {
            LOGGER.error("Printing failed .... ", e);
        } finally {
            LOGGER.info("quit printer task.... ");
        }
    }
    
    private String readTemplate() throws IOException {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(TEMPLATE_NAME)));
            StringBuffer sb = new StringBuffer();
            while(br.ready()) {
                sb.append(br.readLine());
            }
            
            return sb.toString();
        } finally {
            br.close();
        }
    }
    
    private String replaceTokens(String text, Map replacements) {
        Pattern pattern = Pattern.compile("\\[(.+?)\\]");
        Matcher matcher = pattern.matcher(text);
        StringBuffer sb = new StringBuffer();
        while(matcher.find()) {
            String replacement = (String)replacements.get(matcher.group(1));
            if(replacement != null) {
                matcher.appendReplacement(sb, "");
                sb.append(replacement);
            }
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

}
===================================================================

I have posted a sample template which will be used to generate the invoice below. The segments to be replaced are enclosed with square brackets. Replacement of tokens is done in the method replaceTokens(String, Map) method. The keys of the map should map the names of the placeholders which are given within square brackets. Actually the printing is done by onLoad='self.print()' call in the body tag of the template. All what the servlet does is creating the specific invoice for the user and displaying it.

===================================================================
<!DOCTYPE html PUBLIC
'-//W3C//DTD XHTML 1.0 Transitional//EN'
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <title>Untitled Document</title> <style type='text/css'> <!-- ----- css codes ------- --> </style> </head> <body style='font: Arial; color:#333333' onLoad='self.print()'> <br /><br /><br /> <table width='100' border='0' align='center' cellpadding='1' cellspacing='0' bgcolor='#A21D18'> <tr> <td bgcolor='#A21D18'>
<table width='455' border='0' align='left' cellpadding='0' cellspacing='0' bgcolor='#FFFFFF'> <tr> <td width='350' height='30' class='style2'> <div align='left' style='padding-left:30px;'>Date : [date] </div> </td> <td width='350' class='style2'> <div align='left' style='padding-left:30px;'>Time : [time] </div> </td> </tr> <tr> <td colspan='2' class='style2'>
<div align='left' style='padding-left:50px; padding-right:50px; padding-top:5px;'> <table width='100%' border='0' cellspacing='0' cellpadding='0'> <tr> <td width='48%'><u>Invoice</u></td> <td width='4%'>&nbsp;</td> <td width='48%'>&nbsp;</td> </tr> <tr> <td>ID/Passport No </td> <td align='center'>:</td> <td>[nic]</td> </tr> <tr> <td>Name</td> <td align='center'>:</td> <td>[first_name] &nbsp; [last_name]</td> </tr> <tr> <td>Billing Address</td> <td align='center'>:</td> <td>[address]</td> </tr> <tr> <td>Billing Country</td> <td align='center'>:</td> <td>Sri Lanka</td> </tr> <tr> <td>Amount </td> <td align='center'>:</td> <td>LKR [amount]</td> </tr> </table> </div></td> </tr> </table></td> </tr> </table> </body> </html>
===================================================================

The javascript code in the jsp will be opening a new window and loading the invoice for the particular customer. The dimensions of the window opened will be 800 x 600 and will be sending a request to the above servlet with the url 'shopping/pringInvoice'. ['shopping/pringInvoice' is mapped to the servlet in the web.xml file.]
===================================================================

<script type="text/javascript">
  function print() {
    window.open('/shopping/printInvoice','receipt','width=600,height=800');
  }
</script>
===================================================================

The servlet should be defined and mapped in the web.xml.
===================================================================
<servlet-mapping>
<servlet-name>printInvoice</servlet-name>
<url-pattern>/printInvoice</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>printInvoice</servlet-name>
<servlet-class>com.shyarmal.web.services.InvoicePrinter</servlet-class>
</servlet>
==================================================================

thanks,
Shyarmal.

15 comments:

  1. Hi Shyarmal, I am using a jsp page and passing the information to the servlet using requet.getparam and embedding it in html in the servlet. I am required to send the information directly to the printer. It is an invoice how do i call the printer without generating a preview of the page. Kindly assist with sample code. You can email me at romeoebube@gmail.com

    ReplyDelete
    Replies
    1. hi Romeo,
      You can try using 'iframe' element in HTML. Set your printing content as its inner html. Further you'll have to set CSS properties as appropriate to make it invisible and print on load.

      please refer: https://developer.mozilla.org/en-US/docs/Printing
      Hope this would help you.

      Delete
    2. Hi Shyarmal, I took a look at it and it says if you are printing a html page. how about if i have html code for example
      StringBuffer buf = new StringBuffer();
      buf.append(html);
      buf.append(body);
      buf.append(body);
      buf.append(html);
      How would i put this in the iframe. At the moment, I am appending my html to a string buffer because I had it rendered to pdf using itext before printing. Now I want it to print directly without opening the pdf preview.

      Delete
    3. hi Romeo,

      You may set your content as innerHTML of the iframe instead of giving a url to an html page.

      document.getElementById('iframe').contentDocument.write("your content");

      Delete
  2. Thanks so far Shyarmal, Looking at my html code above, would i have multiple lines of the document.getElemet. For example
    document.getElementById('iframe').contentDocument.write(buf.append(html);
    document.getElementById('iframe').contentDocument.write(buf.append(body); etc.
    I am a bit new to this. Thanks

    ReplyDelete
    Replies
    1. No. If you have multiple lines, the content of the iframe would be that you set from the last line. The first gets overwritten by the second.

      You'll have to set the whole content once.

      Delete
  3. The function print page in that link takes in a URL. I am still trying to figure out how to give it the html code which I had to append with a string buffer

    ReplyDelete
    Replies
    1. I assume that you are having the StringBuffer in your servlet. In that case, you'll have to send your html to your jsp before using it in your javascript code.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. Yes I am using the string buffer in the servlet. I need to make it as seamless as possible so the user does not click too much. will it be a good idea to pass it to the jsp and then use javscript onload function to generate the page preview by giving it the jsp url

      Delete
    4. You have to bring the string content to the jsp and do the printing with javascript. Javascript runs on the browser (client side).

      What you suggest is fine too. It all depends on your requirement. However I thought you did not want to show the page before it prints, which is why I suggested iframes.

      Delete
    5. Yes i do not want it to show. But the only way i see fit to use the iframes is to send it to the jsp and then put it in the iframe like the example and then print it onload with javascript. I believe it will be put in the iframe on the jsp page and not the servlet. Do you think it will show this way?

      Delete
    6. I haven't tried it. But I think it would work. :)

      Delete
    7. Okay thanks for your help Shyarmal

      Delete
  4. This comment has been removed by the author.

    ReplyDelete