Tuesday, March 20, 2007

Pack Input Jar Files Dynamically

When host a web site which allows users to download reusable Java class files which are compressed into different jar files, to reduce the download time for the client and the band with usage for the server, it would be nice to provide users an option to be able to download highly compressed packed files. As shown in the snippet of a sample download page, users will select any jar files and choose download them in a highly dense format - pack200.



Pack Compressing Classes

The JSR 200 (Network Transfer Format for JavaTM Archives) specifies a "dense download" format to compress input jar files in pack format. JSR 200 is supported with Java 5. The run-time API to pack and unpack the data and files is abstract class: java.util.jar.pack200.

Pack200 is a abstract class and has one private constructor so it cannot be initialized directly. It provide two static methods to obtain new instance of the Packer engine:


Packer packer = Pack200.newPacker();


and
an instance of the Unpacker engine:


Unpacker unpacker = Pack200.newUnpacker();


Pack Jar Files at Runtime

The following example shows how to convert jar files into a pack format at run-time step by step:

1. Obtain an instance of Packer engine:


Packer packer = Pack200.newPacker();


2. Set up output file stream for output packed stream:


FileOutputStream fos = new FileOutputStream("/tmp/test.pack");


3. Read the input jar into a Jar File input stream:


JarFile jarFile = new JarFile("/path/to/myfile.jar");


4. Call the packer to archive jar file:


packer.pack(jarFile, fos);


5. Close input file streams:


jarFile.close();


If there are more than one input jar files, repeat step 3, 4 and 5.

6. Close output stream:


fos.close();


7. Set content type as "pack200-gzip". This indicates to the server that the client application desires a version of the file encoded with Pack200 and further compressed with gzip:

Unpack Paked Stream

The unpacker engine is used by deployment applications to transform the byte-stream back to JAR format.

The follow steps show how to unpack a packed files:

1. Create an instance of Unpacker engine:


Unpacker unpacker = Pack200.newUnpacker();


2. Read in input packed file and set up output file:


File infile = new File("/tmp/test.pack");

FileOutputStream fostream = new FileOutputStream("/tmp/test.jar");
JarOutputStream jostream = new JarOutputStream(fostream);


3. Call unpacker


unpacker.unpack(infile, jostream);


4. Close file stream:


jostream.close();


References:
  1. JDKTM 5.0 Documentation

  2. Network Transfer Format JSR 200 Specification



Generate Documents With Gridsphere Portlet -- A Bug in build.xml

I wanted to create document for my portlet calledtestportlet created in GridsphereframeworkWhen I run


ant docs


I got errors:


BUILDFAILED
/path/to/gridsphere-2.1.5/projects/testportlet/build.xml:256: No sou rce filesand no packages have been specified.



In the build.xml file the"ant docs" is defined as:

....
<target name="docs" depends="javadocs"description="Create projectdocumentation"/> <!--===================================================================--> <!-- Creates allthe APIdocumentation --> <!--===================================================================--> <targetname="javadocs" depends="setenv" description="CreateJavadocs"> <echo>CreatingJavadocs</echo> <delete quiet="true"dir="${build.javadoc}"/> <mkdirdir="${build.javadoc}"/> <javadocsourcepath="src" classpathref="classpath" destdir="${build.javadoc}" author="true" version="true" splitindex="true" use="true" maxmemory="180m" windowtitle="${project.title}" doctitle="${project.api}"> <!--bottom="Copyright &#169; 2002,2003 GridLab Project. All RightsReserved."> --> </javadoc> </target>
...

I am using Java 1.5 and Apach Ant 1.6. Look like it is the issue of Java 1.5,the sourcepath is ignored even though Irun "javadoc" command from a separatepackage independent of Gridsphere framework. I modified a little bit of thebuild.xml file and now it is workingwell:

1. First remove attribute sourcepath="src"
2. Add "<fileset dir="src" />" to <javadoc> element.

The modified file should look like:

.... <target name="docs" depends="javadocs"description="Create projectdocumentation"/> <!--===================================================================--> <!-- Creates allthe API documentation --> <!--===================================================================--> <targetname="javadocs" depends="setenv" description="CreateJavadocs"> <echo>CreatingJavadocs</echo> <delete quiet="true"dir="${build.javadoc}"/> <mkdirdir="${build.javadoc}"/> <javadocsourcepath="src" classpathref="classpath" destdir="${build.javadoc}" author="true" version="true" splitindex="true" use="true" maxmemory="180m" windowtitle="${project.title}" doctitle="${project.api}"> <filesetdir="src" /> <!--bottom="Copyright &#169; 2002,2003 GridLab Project. All RightsReserved."> --> </javadoc> </target> ...


Save file and run "ant docs" or"ant javadocs", the document files aregenerated in/path/to/gridsphere-2.1.5/projects/testportlet/build/docs/javadocs/.

Commentary:

Some one had mentioned that the combination of ant 1.6 and Java 1.5 wouldigmore attribute "sourcepath" in thebuild file, but I think it is the problem of Java 1.5. Do Gridspherepeople notice that? Anyway, it is still very nice to be able to generatedocuments for portlets from within Gridsphere framework environment if we do alittle bit extra work.

Thursday, March 8, 2007

A Web Form Based PDFs Merger

Task: Provide a web-based application allows users to merger multiple files. Application is running in Grails framework.


Analysis: This problem can be split into three sub-tasks and be solved in three steps: upload multiple pdf files, retrieve submitted files and merger multiple pdfs.

Step1: Upload Multiple PDFs Files


Upload and process multiple files from a Web Form is not a trivial task because file input element allows uploading only one file at a time. Inspired by StickBlog's excellent post: Upload multiple files with a single file element. I decided to use Javascript instead of Applet to achieve this goal. I modified StickBlog's code to accommodate my needs. The user interface pdfmerger.gsp has the following element:


<g:form action="merge" method="post" enctype="multipart/form-data">

<input id='myfile' type='file' name='' onChange="addElement()"></input>
<input type="submit" value="Submit">

<br>Files list (Please note maximun number of uploaded files is 5):
<!-- This is where the output will appear -->
<div id="filesList"></div>
</g:form>


The event onChange of file input is captured. Each time a file is selected, the Javascript function addElement in script.js is invoked


var new_row = document.createElement('div' );


and a new <div> element is created and three elements: a text input box, a button and a file input box are appended to it. The text input box is just for display the file name. You can use other element for this purpose too:


var new_row_input =document.createElement( 'input' );
new_row_input.type = 'text';
new_row_input.name = "ins_" + (childs.length + 1)
new_row_input.value = element.value;


The button is used to delete a corresponding uploaded file if it is clicked on:


var new_row_button =document.createElement( 'input' );

new_row_button.type = 'button';
new_row_button.onclick = function (){
...
...
}


The file input box stored the uploaded files and will be submitted to server, and we like to make it invisible:


var new_row_file_input =document.createElement( 'input' );
new_row_file_input.setAttribute ('name','file_' + count);
new_row_file_input.setAttribute ('id','file_' + count);
new_row_file_input.value = element.value;
new_row_file_input.style.opacity = 0;


Finally, the newly created <div> element is appended to <div>with id"file_list " in pdfmerger.gsp:


new_row.appendChild(new_row_input);
new_row.appendChild( new_row_button );
new_row.appendChild(new_row_file_input);

target_list.appendChild (new_row);


The complete Javascript can be found here.


Step 2: Retrieve Submitted Files

Submitted files are retrieved and processed on server side in acontroller called PdfmergerController.java.In Grails, retrieving files is very easy by using build in Spring file system.We want to retrieve all submitted files and stored into an ArrayList for furtherprocessing.


for (i in 0.. max_num-1){
def file_name = "file_" + i;
def f = request.getFile(file_name);

if(f!=null && !(f.isEmpty())){
//println "file content type " + f.getContentType()
FileInputStream ins = f.getInputStream()
pdfs.add(ins);
}
}


Step 3: Merger Multiple PDFs

Once we have all submitted files in the list pdfs, we are ready to mergerthe PDFs. I just adopted the source code of method "concatPDFs" from Abhi'spost. It works so well with my system. The source code ofPdfmergerController.java can be found here.We added a beforeInterceptor to validatefiles content type before request is processed further.

Commentary

1. If server side processing is done ina J2ee environment, a third party library is needed since Java Servlet and Jspdo not have building mechanism to handle web form based file uploading. I hadApache Jakarta CommonsFileUpload package. It is an open source and can be downloaded from Apache Jakarta CommonsFileUpload project. Another package Apache Jakarta Commons IOproject is also needed internally by FileUpload package.

2. In step 3, we do file content typecheck on server side. However, it is also a good idea to validate content typeat client side when file is uploaded.

3. I just test the sample applicationwith IE and Firefox and no other else.

References

1. Uploadmultiple files with a single file element(StickBlog)
2. MergePDF files with iText (Abhi on Java)
3. Grails onlinetutorial
4. Tutorial: iText ByExample
5. ApacheJakarta Commons FileUpload project
6.
Apache Jakarta Commons IOproject