Archive for the ‘ java ’ Category

While helping a colleague out with a tricky problem accessing Excel files, I downloaded the Java Excel API library from SourceForge. After a quick scan of the documentation, I started to write some ColdFusion code.

<cfscript>
jxlWorkbook = createObject('java','jxl.Workbook');
file = createObject('java','java.io.File').init("C:\\temp\\test.xls");
excelFile = jxlWorkbook.getWorkbook(file);
</cfscript>

I’m immediately alerted with the message

The selected method getWorkbook was not found.

Either there are no methods with the specified method name and argument types,
or the method getWorkbook is overloaded with arguments types that ColdFusion can't
decipher reliably. If this is a Java object and you verified that the method exists,
you may need to use the javacast function to reduce ambiguity.

I dump out the object, and there are actually 5 method signatures for getWorkbook() and the one I want accepts a single argument of type java.io.File. I also know that the JavaCast() function certainly can’t handle a complex type like java.io.File. What’s a poor coder to do? I thought this was the end of the line, but figured I’d try something anyway…

<cfscript>
jxlWorkbook = createObject('java','jxl.Workbook');
excelFile = jxlWorkbook.getWorkbook(createObject('java','java.io.File').init("C:\\temp\\test.xls"));
</cfscript>

Lo, and behold, that worked perfectly! I never knew that you could use createObject() inside a method call.

There was another snag that I’d never ran across before: another method of the library wouldn’t accept integers passed from ColdFusion. In a simple from-to loop producing an index, the value of the index wasn’t being accepted by the method.

<cfloop from="0" to="#transactions.getRows() - 1#" index="row">
	<cfloop from="0" to="#transactions.getColumns() - 1#" index="col">
		<cfset currentCell = transactions.getCell(col, row)>
		<cfoutput>#currentCell.getContents()#</cfoutput>
	</cfloop>
	<br/>
</cfloop>

I had to use JavaCast() to force them into the native int type.

<cfset currentCell = transactions.getCell(JavaCast("int", col), JavaCast("int", row))>

Image manipulation in ColdFusion

Ok, so it’s not really in ColdFusion, but I wrong a Java library so that I could access image manipulation routines in my ColdFusion apps. Using the Java Advanced Imaging (JAI) API made it very easy to write a library to perform a variety of functions.

Requirements

The Java 1.5 JDK must be installed on your system and used to compile this code (or to use the library I provide). By default ColdFusion 6 and 7 support up to 1.4 JDK, so this is an unsupported bit of code.

Install the JAI Build

You can visit the Java Advanced Imaging Project page to download the binary for your OS of choice. By default the installation will place the files in your existing JDK or JRE location (for example, on Windows it will be something similar to C:\Program Files\Java\jre1.5.0_05), but you can override this and have the files installed directly into the LIB directory of your application server - for example - C:\jboss-4.0.4\server\default\lib.

Write a Java imaging class

Now we can start using those libraries to build a Java class, which you can use in ColdFusion. This is my simple class which you can use as a reference. I put in my notes and added links to the JAI specs page for specific operations.

package orbwave.ImageController;

/*
 * Import the JAI libraries and the IO libraries to read, manipulate and save
 * an image file.
 */
import java.io.*;
import java.awt.image.renderable.*;
import javax.media.jai.*;
import com.sun.media.jai.codec.*;

public class Controller {

	// Private member variable to hold the original iamge file
	private RenderedOp sourceFile = null;

	/*
	 * Load a source image into a FileSeekableStream object for manipulation
	 */
	public void load(String file) throws IOException
	{
	  FileSeekableStream fss = new FileSeekableStream(file);
	  sourceFile = JAI.create("stream", fss);
	}

	/*
	 * Save the manipulated image to the specified file location, with the
	 * specified type.  Set the sourceFile reference back to null to prevent
	 * the file from being locked by this process
	 */
	public void save(String file, String type) throws IOException
	{
	  FileOutputStream os = new FileOutputStream(file);
	  JAI.create("encode", sourceFile, os, type, null);
	  sourceFile = null;
	  os = null;
	}

	/*
	 * For cropping, I provide a simple 'centering' crop where the starting edges
	 * and the max edges are all equal
	 *
	 * Documentation:
	 * http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/operator/CropDescriptor.html
	 */
	public void crop(float edge)
	{
	  ParameterBlock params = new ParameterBlock();
	  params.addSource(sourceFile);
	  params.add(edge);
	  params.add(edge);
	  params.add((float) sourceFile.getWidth() - edge);
	  params.add((float) sourceFile.getHeight() - edge);
	  sourceFile = JAI.create("crop", params);
	}

	/*
	 * This scale method does an incremental process of scaling until the desired width
	 * is achieved.  This process provides a MUCH smoother final image than a direct scale from
	 * the original size.
	 *
	 * Documenation:
	 * http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/operator/ScaleDescriptor.html
	 */
	public void scaleWidth(float width)
	{
		ParameterBlock params;
		while (width < sourceFile.getWidth()) {
			float esc = Math.max(((float) (width) / sourceFile.getWidth()),0.5f);
			params = new ParameterBlock();
			params.addSource(sourceFile);
			params.add(esc).add(esc).add(0.0F).add(0.0F);
			params.add(Interpolation.getInstance(Interpolation.INTERP_BICUBIC));
			sourceFile = JAI.create("scale", params);
		}
	}

	/*
	 * This scale method does an incremental process of scaling until the desired height
	 * is achieved.  This process provides a MUCH smoother final image than a direct scale from
	 * the original size.
	 *
	 * Documenation:
	 * http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/operator/ScaleDescriptor.html
	 */
	public void scaleHeight(float height)
	{
		ParameterBlock params;
		while (height < sourceFile.getHeight()) {
			float esc = Math.max(((float) (height) / sourceFile.getHeight()),0.5f);
			params = new ParameterBlock();
			params.addSource(sourceFile);
			params.add(esc).add(esc).add(0.0F).add(0.0F);
			params.add(Interpolation.getInstance(Interpolation.INTERP_BICUBIC));
			sourceFile = JAI.create("scale", params);
		}
	}

	/*
	 * This operation rotates an image about a given point by a given angle, specified
	 * in degrees. The origin defaults to (0, 0).
	 *
	 * Documentation:
	 * http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/operator/RotateDescriptor.html
	 */
	public void rotate(float degrees)
	{
	  ParameterBlock params = new ParameterBlock();
	  params.addSource(sourceFile);
	  params.add((float)sourceFile.getWidth() / 2);
	  params.add((float)sourceFile.getHeight() / 2);
	  params.add(degrees);
	  params.add(Interpolation.getInstance(Interpolation.INTERP_BICUBIC_2));//interpolation method
	  sourceFile = JAI.create("rotate", params);
	}

	/*
	 * To create a thumbnail, I simply accept the maximum edge length wanted for the entire image
	 * and then compare with the width and height to determine which aspect should be used for
	 * resizing.
	 *
	 * Documenation:
	 * Refer to scaleHeight() or scaleWidth() methods for scale documentation
	 */
	public void createThumbnail(float edgeLength)
	{
	  boolean useHeight = (sourceFile.getHeight() > sourceFile.getWidth());
	  while ( edgeLength < ((useHeight) ? sourceFile.getHeight() : sourceFile.getWidth()) ) {
		  float esc = Math.max(((float) (edgeLength) / ((useHeight) ? sourceFile.getHeight() : sourceFile.getWidth()) ),0.5f);
		  ParameterBlock params = new ParameterBlock();
		  params.addSource(sourceFile);
		  params.add(esc);
		  params.add(esc);
		  params.add(0.0F);
		  params.add(0.0F);
		  params.add(Interpolation.getInstance(Interpolation.INTERP_BICUBIC_2));//interpolation method
		  sourceFile = JAI.create("scale", params);
	  }
	}
}

You can download the compiled library and place it in your WEB-INF\lib directory for your application.

orbwave.jar location

Control images via ColdFusion

After you’ve compiled your library, or downloaded my sample, you should restart your application server. Once it has restarted, you can start using the manipulation code in ColdFusion. Here’s some sample code.


<cfscript>
// Create an instance of the image editor
img = createobject("java", "orbwave.ImageController.Controller");

// Load a source file, scale to width to 320 pixels and save it
img.load('C:\\jboss-4.0.4\\server\\default\\deploy\\brownlees.war\\img\\DSC00121.JPG');
img.scalewidth(320);
img.save('C:\\jboss-4.0.4\\server\\default\\deploy\\brownlees.war\\img\\DSC00121_scaled.JPG','jpeg');

// Load a source file, scale to height to 320 pixels, rotate by 90 degrees and save it
img.load('C:\\jboss-4.0.4\\server\\default\\deploy\\brownlees.war\\img\\DSC00121.JPG');
img.scaleheight(320);
img.rotate(45);
img.save('C:\\jboss-4.0.4\\server\\default\\deploy\\brownlees.war\\img\\DSC00121_sclaed_rotated.JPG','jpeg');
</cfscript>

When to use Java over ColdFusion - Part I

I’m investigating some basic, everyday operations that many web application developers face.  Using very simple test cases, I’m determining if there is a time savings by writing Java code and calling it from ColdFusion.  If there is a savings, is it worth the extra coding time and overhead* for using Java.  Here’s the result of my first, basic test.

File Read/Write Operations

I was actually surprised how much faster invoking a Java I/O class was than the equivalent code in ColdFusion.  Using only a 90k byte file, I wrote a Java class to read it, parse it on a delimiter of the pattern ‘x\s’ and output each line.

public String outputScannedFile() throws IOException
{
   Scanner s = null;
   String output = "";
   try {
       s = new Scanner(new BufferedReader(new FileReader("C:\\inputfile\\xanadu.txt")));
       s.useDelimiter("x\\s*");
       while (s.hasNext()) {
      	  output += s.next();
       }
   } finally {
       if (s != null) s.close();
   }
   return output;
}

This is executed by creating an instance of a class and invoking a method (I use cfscript for Java objects… easier to read)

<cfscript>
output = createobject("java", "orbwave.StringTest");
</cfscript>
<cfdump var="#output.outputScannedFile()#">

This executed at a good pace.

Execution Time

Total Time Avg Time Count Template
3078 ms 3078 ms 1 C:\jboss-4.0.4\server\default\.\deploy\brownlees.war\scribble.cfm

The equivalent code in ColdFusion is much easier to write

<cffile action="read" file="C:\\inputfile\\xanadu.txt" variable="input">
<cfloop from="1" to="#ListLen(input,'x')#" index="span">
	<cfoutput>#ListGetAt(input,span,'x')#</cfoutput>
</cfloop>

It also took over twice as long to execute.

Execution Time

Total Time Avg Time Count Template
7484 ms 7484 ms 1 C:\jboss-4.0.4\server\default\.\deploy\brownlees.war\scribble.cfm

As one would expect, using Java to handle the I/O is faster. However, I did not expect the compilation and execution of a simple set of ColdFusion commands to take over 4 seconds longer on such a basic operation.

Is It Worth It?

In situations where you’re performing a significant amount of file I/O, I would suggest writing some simple Java classes, compressing them to a JAR and installing it on your app server.

* By overhead, I’m not talking about system resource overhead, but the additional resources needed to maintain Java in addition to ColdFusion, such as source control measures, training (if necessary), compile-time and build-time.

I stumbled across this great article that Terry Ford wrote when CFMX 6.1 was released.  It’s still relevant and one of the best beginner tutorials on Java resource usage I’ve seen.

Macromedia - Developer Center : Using Java and J2EE Elements in ColdFusion MX Applications

Server Scope Enhancer

Now that the ColdFusion server runs on top of a Java subsystem, I thought it was time for the Server scope to actually contain information about the Java server as well as the CFML processor. The Enhancer adds a new JVM key to the Server scope and adds more keys to the Server.OS structure.

*click image to view full size
Enahncer-781708.jpg

New keys:

  • server.jvm.memory
  • server.jvm.memory.pools
  • server.jvm.threads
  • server.jvm.arguments
  • server.os.CommittedVirtualMemorySize
  • server.os.FreePhysicalMemorySize
  • server.os.FreeSwapSpaceSize
  • server.os.ProcessCpuTime
  • server.os.TotalPhysicalMemorySize
  • server.os.TotalSwapSpaceSize
  • server.os.AvailableProcessors
  • server.os.MBeanInfo

Of course, you can remove any information that you don’t want from the code, but even on my wimpy 500MB RAM/1.33 MHz laptop, execution time for the code was max 67ms and averaged about 32ms.

I made two versions of the code. All of the features descibed above are in the JDK 1.5 version. The 1.4 version still adds some memory values and has the Thread query, but the 1.4 JDK just doesn’t have as many features.

Download Server Scope Enhancer JDK 1.5
Download Server Scope Enhancer JDK 1.4