Site Network: Personal | Professional | Photography

Technical Blog

This blog will contain content related to Java, Seam, Security, my sites and projects, as well as other technical subjects I am interested in.

Comments and questions are welcome!

Seam EntityHome Design Pattern

Friday, April 11th, 2008

I've been using Seam for over a year. At some point the "Home" object was introduced to the documentation (Chapter 11). Reading the documentation didn't convince me of the point. Being an ATG guy at heart, I still prefer using "form handlers" for managing my important entities. So I haven't bothered.

However, just recently I ran into a little problem with LazyInitializationExceptions. I'm sure you've run into them yourself. Basically, when Hibernate loads an entity for you, it's loaded by an entity manager which is available to manage that object within a specific scope. This effects persisting changes. Also, if you have properties on that entity that have a fetchType.LAZY, those properties can only be lazily loaded while the same entity manager is available. If it's not, you get LazyInitializationExceptions. No fun.

So in Seam, what I usually do to avoid this, is to create a long running conversation, and load the entity within the context of that conversation. Then, as long as you're still in that long running conversation, you can lazily load all the properties you want.

Normally this is fine. However, in my latest project the application will send e-mails to users when they get a new inter-user message. If they click on the link in the e-mail, they come to the site, but without the existing long running conversation query parameter. Their user component is session scoped, so they're still logged in, but the user object is now outside of it's conversation, and if you attempt to access lazily loaded properties, for instance the user's messages they are trying to see based on the e-mail, blammo: exception central. Unhappy users.

I was pointed to this page: Using EntityHome for entities in long-running contexts

Which showed me how to use the EntityHome to avoid the whole problem. Basically it works like this:

When the user logs in, set the user entity's id into a session scoped component. Don't bother with long running conversations (at least not for the user). The User Home component's Factory method creates a user component using the session scoped user id, anytime the user component is referenced. This component entity is loaded within the context of the current conversation (if it hasn't already been loaded). So, presto-magic, no lazy loading issues.

It let me fix the issue with about 20 lines of code, and little trouble. It's working perfectly so far.

10MinuteMail Updates

Sunday, March 23rd, 2008

I just pushed a new version of 10MinuteMail. Here are the notable updates:

  1. Removed the Ad-Aware links and text. No one was clicking on them anyhow.
  2. Added some translation fixes.
  3. Implemented AJAX based (RichFaces) refreshing of the list of e-mails in your inbox.
  4. Added smtp client throttling (in Postfix) to limit the number of messages accepted from a single source within 60 seconds. This seems to have already fixed the negative impact of high volume spammers on the function of the site.
  5. Removed the "Get Another E-Mail" feature. While this was a user request, I discovered that it was being abused by spammers.
  6. Added a Forward feature to allow you to forward a received e-mail to your home account for storage.

Enjoy! If you have any issues with the AJAX refreshes, let me know, but I think it should work better now.

How To Resize Uploaded Images Using Java - Better Way

Thursday, March 13th, 2008

Based on helpful comments from Matt on this previous post:

How To Resize Uploaded Images Using Java

I have upgraded the image resizing code. The results are noticeably better in quality, even at thumbnail sizes. I wanted to share the completed new code, in case anyone needs it.

Again, thanks to Matt S. for his pointers.

 
    /**
     * The JAI.create action name for handling a stream.
     */
    private static final String JAI_STREAM_ACTION = "stream";
 
    /**
     * The JAI.create action name for handling a resizing using a subsample averaging technique.
     */
    private static final String JAI_SUBSAMPLE_AVERAGE_ACTION = "SubsampleAverage";
 
    /**
     * The JAI.create encoding format name for JPEG.
     */
    private static final String JAI_ENCODE_FORMAT_JPEG = "JPEG";
 
    /**
     * The JAI.create action name for encoding image data.
     */
    private static final String JAI_ENCODE_ACTION = "encode";
 
    /**
     * The http content type/mime-type for JPEG images.
     */
    private static final String JPEG_CONTENT_TYPE = "image/jpeg";
 
    private int mMaxWidth = 800;
 
    private int mMaxWidthThumbnail = 150;
 
.....
 
    /**
     * This method takes in an image as a byte array (currently supports GIF, JPG, PNG and
     * possibly other formats) and
     * resizes it to have a width no greater than the pMaxWidth parameter in pixels.
     * It converts the image to a standard
     * quality JPG and returns the byte array of that JPG image.
     *
     * @param pImageData
     *                the image data.
     * @param pMaxWidth
     *                the max width in pixels, 0 means do not scale.
     * @return the resized JPG image.
     * @throws IOException
     *                 if the image could not be manipulated correctly.
     */
    public byte[] resizeImageAsJPG(byte[] pImageData, int pMaxWidth) throws IOException {
	InputStream imageInputStream = new ByteArrayInputStream(pImageData);
	// read in the original image from an input stream
	SeekableStream seekableImageStream = SeekableStream.wrapInputStream(imageInputStream, true);
	RenderedOp originalImage = JAI.create(JAI_STREAM_ACTION, seekableImageStream);
	((OpImage) originalImage.getRendering()).setTileCache(null);
	int origImageWidth = originalImage.getWidth();
	// now resize the image
	double scale = 1.0;
	if (pMaxWidth > 0 && origImageWidth > pMaxWidth) {
	    scale = (double) pMaxWidth / originalImage.getWidth();
	}
	ParameterBlock paramBlock = new ParameterBlock();
	paramBlock.addSource(originalImage); // The source image
	paramBlock.add(scale); // The xScale
	paramBlock.add(scale); // The yScale
	paramBlock.add(0.0); // The x translation
	paramBlock.add(0.0); // The y translation
 
	RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING,
		RenderingHints.VALUE_RENDER_QUALITY);
 
	RenderedOp resizedImage = JAI.create(JAI_SUBSAMPLE_AVERAGE_ACTION, paramBlock, qualityHints);
 
	// lastly, write the newly-resized image to an output stream, in a specific encoding
	ByteArrayOutputStream encoderOutputStream = new ByteArrayOutputStream();
	JAI.create(JAI_ENCODE_ACTION, resizedImage, encoderOutputStream, JAI_ENCODE_FORMAT_JPEG, null);
	// Export to Byte Array
	byte[] resizedImageByteArray = encoderOutputStream.toByteArray();
	return resizedImageByteArray;
    }
 

login-required=”true” Will End Your Conversation

Monday, March 10th, 2008

In Seam, in the pages.xml or mypage.page.xml files, you note that a given page requires the user to be logged in to view the page. It is a very easy way of handling simple security. What happens is if a user attempts to access a page with the login-required="true" attribute and they are not logged in, they are automatically redirected to your login page (as defined in your pages.xml). Once they login, they are automatically redirected back to the page they had attempted to access initially. Very elegant, and much easier than writing all that yourself.

There is however a "gotcha" you should be aware of in this flow. If you use the login page, or logic, to start a long running conversation (I do this because my User entity object has some large binary properties which I want lazily loaded), the post-login redirection will kill that conversation. In my case this led to lazy loading exceptions down the road.

It took me a while to figure out the cause, as it only happened much later in the application, and most of the time it worked (when I logged in purposefully from the main page), but sometimes would break (when I'd had an expired session and gone through the login auto-redirection flow). Eventually though I was able to track it down to those two scenarios.

What is happening is this: before the first redirection happens (to the login page), the Seam Redirect classes captureCurrentView method is called. This basically saves the information about the page you were trying to view (it's a little more complex than that- being all Faces like and whatnot, but that's the gist of it). This method creates a long running conversation in order to hold this state across the multiple pages. If there was no previous long running conversation to join, and it had to create a new conversation, it stores a conversationBegun flag of true.

Then you hit the login page, where the page (or code) would Begin a long running conversation, except you almost certainly have join=true, so it joins the existing long running conversation, which was created by the captureCurrentView method. This is the conversation your User entity was loaded up in.

After your login was successful, the Redirect classes returnToCapturedView method is called to send you back to where you'd tried to go initially.

This method has the following block:

 
if (conversationBegun) {
       Conversation.instance().end();
}
 

This ends the conversation you had loaded your User entity within, and when you hit your destination page, there is no long running conversation at all. Then, a few minutes later, it's lazy loading exception time.

The downside is there isn't a really clean fix that I am aware of yet.

The upside is there is a pretty simple hack: just add a <begin-conversation join="true" /> to all of your pages which have the login-required="true" attribute. This creates a new long running conversation before the Redirect does it's work, so it in turn joins that conversation instead of creating a new one, the conversationBegun flag is false, and the returnToCapturedView method doesn't end your conversation. It's a hack, and you have to remember to add it to every page which requires a logged in state, but it does work.

How To Resize Uploaded Images Using Java

Thursday, March 6th, 2008

I am building a Seam application which need to support users uploading images, and then the site displaying them. I wanted to resize big images into something reasonable (say 1024 or 800 px wide) and generate a thumbnail, and I also wanted to standardize on a single image format just to make things easy and consistent.

So I needed method that would take an uploaded image as a byte array (byte[]) and would resize the image (if needed) and convert it to a fixed quality JPG, and give me back a byte array to store in the database. I am using Seam, so I get a handy byte array, but this method should work fine for non-Seam applications as well.

 
    /**
     * This method takes in an image as a byte array (currently supports GIF, JPG, PNG and possibly other formats) and
     * resizes it to have a width no greater than the pMaxWidth parameter in pixels. It converts the image to a standard
     * quality JPG and returns the byte array of that JPG image.
     *
     * @param pImageData
     *                the image data.
     * @param pMaxWidth
     *                the max width in pixels, 0 means do not scale.
     * @return the resized JPG image.
     * @throws IOException
     *                 if the iamge could not be manipulated correctly.
     */
    public byte[] resizeImageAsJPG(byte[] pImageData, int pMaxWidth) throws IOException {
	// Create an ImageIcon from the image data
	ImageIcon imageIcon = new ImageIcon(pImageData);
	int width = imageIcon.getIconWidth();
	int height = imageIcon.getIconHeight();
	mLog.info("imageIcon width: #0  height: #1", width, height);
	// If the image is larger than the max width, we need to resize it
	if (pMaxWidth > 0 && width > pMaxWidth) {
	    // Determine the shrink ratio
	    double ratio = (double) pMaxWidth / imageIcon.getIconWidth();
	    mLog.info("resize ratio: #0", ratio);
	    height = (int) (imageIcon.getIconHeight() * ratio);
	    width = pMaxWidth;
	    mLog.info("imageIcon post scale width: #0  height: #1", width, height);
	}
	// Create a new empty image buffer to "draw" the resized image into
	BufferedImage bufferedResizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
	// Create a Graphics object to do the "drawing"
	Graphics2D g2d = bufferedResizedImage.createGraphics();
	g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
	// Draw the resized image
	g2d.drawImage(imageIcon.getImage(), 0, 0, width, height, null);
	g2d.dispose();
	// Now our buffered image is ready
	// Encode it as a JPEG
	ByteArrayOutputStream encoderOutputStream = new ByteArrayOutputStream();
	JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(encoderOutputStream);
	encoder.encode(bufferedResizedImage);
	byte[] resizedImageByteArray = encoderOutputStream.toByteArray();
	return resizedImageByteArray;
    }
 

In my application I call this method twice, once to convert the uploaded image into a limited size JPG, and then once again to generate a much smaller thumbnail. I store both of these in the database and will use caching at the Apache layer to ensure performance.