Pages

Monday, April 14, 2008

PDF previewer in jdeveloper 10.1.3

This blog I will show you how can make a free pdf previewer by generating images of pdf files and stream this png with a servlet to the objectImage of the jsf page.
If you have a web application where people can download pdf files, then this can be handy to provide a sort of previewer where the first page is shown, so they don't have to download or open every pdf file in acrobat reader to find the right document.
I use for the pdf to image generation the library of jpedal and some great code of Richard Braman for the scaling and generating of the png files.
Here is how it looks. First you have to select a pdf document.

If we press the load button then the first page is shown with the total pagecount of the document.


Here is the jsf page.

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces"
xmlns:afh="http://xmlns.oracle.com/adf/faces/html"
xmlns:cust="http://xmlns.oracle.com/adf/faces/customizable">
<jsp:output omit-xml-declaration="true" doctype-root-element="HTML"
doctype-system="http://www.w3.org/TR/html4/loose.dtd"
doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/>
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<f:view>
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=windows-1252"/>
<title>view</title>
</head>
<body>
<h:form id="form1">
<af:panelGroup layout="vertical">
<af:panelGroup layout="horizontal">
<af:selectOneChoice id="choice" label="Document"
valueChangeListener="#{ViewerBean.changeChoice}">
<af:selectItem label="doc1" value="javascript"/>
<af:selectItem label="doc2" value="threads"/>
</af:selectOneChoice>
<af:commandButton text="Load" id="laden"/>
</af:panelGroup>
<af:panelGroup rendered="#{ViewerBean.showImage}" >
<af:commandButton text="Previous" id="vorige" actionListener="#{ViewerBean.vorigePagina}">
</af:commandButton>
<af:outputText value="#{ViewerBean.pagesOverview}"/>
<af:commandButton text="Next" id="volgende" actionListener="#{ViewerBean.volgendePagina}">
</af:commandButton>
</af:panelGroup>
<af:objectImage id="objectImage1"
partialTriggers="volgende vorige laden"
binding="#{ViewerBean.image}" rendered="#{ViewerBean.showImage}" />
</af:panelGroup>
</h:form>
</body>
</html>
</f:view>
</jsp:root>

I have to use partialtriggers on the objectImage else it won't refresh from the servlet if I press the load or previous / next buttons.

In the faces-config.xml I have defined two beans, the first is for the jsf page and the second is for the servlet. This hashmap I use to pass the parameters to the servlet

<managed-bean>
<managed-bean-name>ViewerBean</managed-bean-name>
<managed-bean-class>nl.ordina.pdf.backing.Viewer</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>ImageParametersBean</managed-bean-name>
<managed-bean-class>java.util.HashMap</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

The backing bean of the jsf page. Basically I fill the servlet hashmap with the right values.

package nl.ordina.pdf.backing;

import java.util.HashMap;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
import nl.ordina.pdf.ImageGenerator;
import oracle.adf.view.faces.component.core.output.CoreObjectImage;

public class Viewer {

private String tempPath = "d:/temp/";
private String imageUrl = "http://XPCND7010XMP:8988/pdf_previewer-ViewController-context-root/preview";
private String pdfArchivePath = "D:/documenten/MyeBooks/";
private int page = 1;
private int totalPages = 1;
private String pagesOverview;
private ImageGenerator img = new ImageGenerator();
private CoreObjectImage image;
private boolean showImage = false;
private HashMap parameters;

public Viewer() {
parameters = getHashMapServlet();
parameters.put("temppath",tempPath);
parameters.put("pdfarchivepath",pdfArchivePath);
}

public void volgendePagina(ActionEvent event){
if ( page <> 1 ){
page = page -1;
setServletPage(page);
}
}

public void changeChoice(ValueChangeEvent valueChangeEvent) {
page = 1;
setServletPage(page);
setServletDocument(valueChangeEvent.getNewValue().toString());
image.setSource(imageUrl);
showImage = true;
totalPages = img.pdfTotalPages(pdfArchivePath,valueChangeEvent.getNewValue().toString());
}

private HashMap getHashMapServlet(){
FacesContext fctx = FacesContext.getCurrentInstance();
return (HashMap)fctx.getApplication().createValueBinding("#{ImageParametersBean}").getValue(fctx);
}
private void setServletDocument(String doc){
parameters.put("document",doc);
}
private void setServletPage(int page){
parameters.put("page",page);
}

public void setImage(CoreObjectImage image) {
this.image = image;
}

public CoreObjectImage getImage() {
return image;
}

public void setShowImage(boolean showImage) {
this.showImage = showImage;
}

public boolean isShowImage() {
return showImage;
}

public String getPagesOverview() {
pagesOverview = page + " / " + totalPages;
return pagesOverview;
}
}


The servlet code which reads the hashmap , generates the png and streams it back to the jsf page

package nl.ordina.pdf.servlet;

import java.io.IOException;
import java.util.HashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import nl.ordina.pdf.ImageGenerator;

public class PreviewServlet extends HttpServlet {

public void init(ServletConfig config) throws ServletException {
super.init(config);
}

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HashMap parameters = (HashMap)request.getSession().getAttribute("ImageParametersBean");
Integer page = 1;
String documentName = null, temp = null, archive = null;

if (parameters != null) {
documentName = parameters.get("document").toString();
page = new Integer(parameters.get("page").toString());
temp = parameters.get("temppath").toString();
archive = parameters.get("pdfarchivepath").toString();
}
ImageGenerator img = new ImageGenerator();
byte[] data = img.getPic(documentName,page.intValue(),temp,archive );
response.setContentType("image/png");
response.setContentLength( data.length );
response.getOutputStream().write( data );
response.getOutputStream().flush();
response.setHeader("Cache-Control", "no-cache");
}
}

Here is the example project with the jpedal code for the png generations.

To get this working change the select one choice of the jsf page with not secured pdf files and without the .pdf extension. Change the variables in the viewer bean with your own values

private String tempPath = "d:/temp/";
private String imageUrl = "http://XPCND7010XMP:8988/pdf_previewer-ViewController-context-root/preview";
private String pdfArchivePath = "D:/projecten/justid/cdd_archief/PEN_ARCH/01/01/45/";

Update
JPedal released a new jar where they changed something and you can get the following exception 08/05/26 17:07:21 java.awt.HeadlessException
08/05/26 17:07:21 at sun.java2d.HeadlessGraphicsEnvironment.getDefaultScreenDevice(HeadlessGraphicsEnvironment.java:65)

Here is the code of the new imagegenerator

package nl.ordina.pdf;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.jpedal.*;
import org.jpedal.exception.PdfException;
import java.io.*;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;


public class ImageGenerator {
public ImageGenerator() {
}

public byte[] getPic( String document,Integer picture
, String temp, String archive, String generateAlways) {
byte[] data = null;
String path = temp+document+"_"+picture+".png";

try {
boolean exists = (new File(path)).exists();
if (exists && generateAlways.equals("no") ) {
data = read(path);
} else {
path= PDFToPic(archive,document,picture,temp);
data = read(path);
}
} catch (IOException e) {
e.printStackTrace();
}
return data;

}

public int pdfTotalPages(String filepath,String doc)
{
try
{
PdfDecoder decoder = new PdfDecoder();
decoder.openPdfFile(filepath+doc+".pdf");
if (decoder.isFileViewable()) {

int PageCount = decoder.getPageCount() ;
return PageCount;
}
} catch(Exception err) {
err.printStackTrace();
}
return 0;
}



public byte[] read(String file) throws IOException {
InputStream in = new FileInputStream(file);
byte[] data = new byte[in.available()];
in.read(data);
return data;
}

public String PDFToPic(String filepath,String doc, int picture,String temppath)
{
try
{
PdfDecoder decoder = new PdfDecoder();
decoder.openPdfFile(filepath+doc+".pdf");
if (decoder.isFileViewable()) {

int PageCount = decoder.getPageCount() ;
double ImageWidth = 0;
double ImageHeight = 0;

if ( picture <= PageCount) {
//generate png files
generatePNGfromPDF(decoder, picture, ImageWidth, ImageHeight, doc,temppath);
return temppath+doc+"_"+picture+".png";

} else {
generatePNGfromPDF(decoder, PageCount, ImageWidth, ImageHeight, doc,temppath);
return temppath+doc+"_"+PageCount+".png";

}
}
} catch(Exception err) {
err.printStackTrace();
}
return null;
}


private void generatePNGfromPDF( PdfDecoder decoder
, int PageNumber
, double width
, double height
, String doc
, String temppath){
//int size = 100;
decoder.useHiResScreenDisplay(true);
decoder.setExtractionMode(PdfDecoder.RAWIMAGES+PdfDecoder.FINALIMAGES);


int scaling = 100;

try {
BufferedImage PDF = decoder.getPageAsImage(PageNumber);
if(scaling!=100){
int newWidth=PDF.getWidth()*scaling/100;
//only 1 new co-ord needed so use -1 for other as aspect ration does not change
Image scaledImage= PDF.getScaledInstance(newWidth,-1,BufferedImage.SCALE_SMOOTH);
PDF = new BufferedImage(scaledImage.getWidth(null),scaledImage.getHeight(null) , BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = PDF.createGraphics();
g2.drawImage(scaledImage, 0, 0,null);

}
File fileToSave = new File( temppath+doc+"_"+PageNumber+".png");
boolean failed=decoder.getObjectStore().saveStoredImage(temppath+doc+"_"+PageNumber+".png",PDF,true,false,"png");
} catch (PdfException e) {
// TODO
e.printStackTrace();
}

}

}

11 comments:

  1. Hi
    i know this comment very late but i need it know.

    i migrate your example to jdve11g and it work .

    the Load button working and the firist page loaded. and get total pages correct.

    but NEXT/PREVIOUS button not working.

    i add some print stm within event method
    but it is not printted (as not enter the event method)

    if i press next or previous only as re rendering showImage to false

    i hope you help me..this is important for me

    thanks

    ReplyDelete
  2. Ok

    I will take a look.
    Is the page full of trinidad components or with 11g rich faces.

    thanks Edwin

    ReplyDelete
  3. Hi
    thank you for response

    I use it as part of page within panel suplter component and I try each trinidad components and ADF rich components 11g but not working


    i try with spcial page for it but with our ADF template also not working

    i will try without any adf component ...

    thnkyou again

    ReplyDelete
  4. Hi I made 11g version with some extra features.

    check my blog for the new version

    ReplyDelete
  5. Here is the url of 11g version http://biemond.blogspot.com/2008/11/jdeveloper-11g-pdf-previewer.html

    cheers edwin

    ReplyDelete
  6. Hi,

    I am using Jdeveloper 10.1.3 ADF BC. I have stored PDF files in the database. How do i open these pdf files though my web app. How do i open it using an adobe reader.

    regards,
    Sudha

    ReplyDelete
  7. Ok,

    first you have to make a servlet with a parameter like you primary key of your blob table.

    in the servlet you can use an ejb / plain jdbc or adf bc to retrieve the blob.

    convert the blob to a bytearray . set the mimetype to pdf and stream it to the client en voila it opens acrobat reader in your browser.

    thanks Edwin

    ReplyDelete
  8. Hi,

    I don't what i am doing wrong
    OutputStream out = response.getOutputStream();
    InputStream in = imageFile.getBinaryStream();
    fileType = MimeTypes.getMimeType(fileName);
    response.setContentType(fileType);
    response.setContentLength((int)length.intValue());
    byte[] buf = new byte[10 * 1024];
    int count;
    while ((count = in.read(buf)) >= 0) {
    out.write(buf, 0, count);
    }
    in.close();
    out.flush();
    out.close();

    In my jsf -->afobjectImage source="/imageServlet?docNo={processScope.docKey}/>


    Thanks

    ReplyDelete
  9. Hi,

    can you send me a small testcase biemond at gmail dot com

    thanks Edwin

    ReplyDelete
  10. Hi Edwin,

    Can tell us how to read pdf from the bpel process and get that content later mapped to the db..Please let us know how to read pdf file using java embedded activity...Thanks

    ReplyDelete
  11. Hi,

    you can try to use the itext framework. but I dont know you can read all the data. you can retrieve the common pdf attributes.

    just store it in the db with oracle text you can do a lot

    thanks

    ReplyDelete