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!

Archive for the ‘Seam’ Category

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.

Displaying and Rerendering a RichFaces ModalPanel from a commandLink

Tuesday, March 4th, 2008

Let's say that when a user clicks on something, perhaps selecting something from a DataTable or DataGrid, you want to call some server side code, and then popup a modal panel which needs to show content based on the new state setup by your server side call. In my case I have a DataGrid showing a number of items, and when a user clicks on an item I want to set the current selected item on a Seam component on the server, and then popup a modal panel to display detailed information about the item, and provide the user some actions within the modal panel.

Looking at the solution it seems so simple, but for some reason it took me hours of trying different combinations, orders, tags, JavaScripts, etc... before I could get this to work correctly. I had the panel appearing with the old data, or appearing with the old data and then disappearing completely, only to show the so called new data the next time I clicked on an item, or rendering on the page itself below the footer, or not popping up at all, or popping up with null data, or.... So I'm posting it up here for myself and anyone else who runs into issues.

First, create your modal panel (rich:modalPanel). I do this in a separate .xhtml file, as I will use the same model panel in various pages throughout the site. In your modal-panel.xhtml file (or whatever you choose to name it), start with a <ui:composition> tag, define your RichFaces xml namespace (and any other JSF libs you'll be using), and then setup your <rich:modalPanel>. You need to give the modalPanel a unique id, in this case we'll call it "myModalPanel".

<rich:modalPanel id="myModalPanel" autosized="true" zindex="2000">

Inside your modalPanel tag, you need to define an inner div. This is the div which you will be rerendering after the server action is complete. I use tags, as I'm using Seam. Ensure that any references to backing beans or data that you want refreshed are within these div tags. You must give this div a unique id as well, in this case we'll call it "myModalDiv".

<s:div id="myModalDiv">

The one limitation that I've run into here, and if you know how to solve this, please let me know, is that the <f:facet> tags for your header text and controls can't be inside this div, and you can't put the div around the whole modalPanel (or it makes the panel disappear when you rerender). As such you can't make anything from the <f:facet>s be updated on rerender. This is preventing me from having the modal panel header displaying the current selected item's name. Not great, but not the end of the world.

So inside this div you have your dynamic output. I have lots of

<h:outputText value="#{backingBean.selectedItem.property}" />

type entries in mine.

On your actual page where you will be displaying the modal panel from you need to include the modal-panel.xhtml file, I use a:

<a:include viewId="/modal-panel.xhtml" />

Then, I have my page, which includes a rich:dataGrid, and inside it has an <a:commandLink> which creates the AJAX clickable item. This commandLink has to call the backend action, display the modal panel, and rerender your div.

 
<a:commandLink id="showItem" action="#{backingBean.setSelectedItem(item)}"
oncomplete="Richfaces.showModalPanel('myModalPanel',{width:550, top:200});"
reRender="myModalDiv">
    Click Here!
</a:commandLink>
 

This makes the panel display, and then the new content is rendered in the panel. It seems to work just how I wanted it to. With the minor annoyance of having to use a static modalPanel header.

How to get RichFaces to not crash Safari

Tuesday, March 4th, 2008

RichFaces, a collection of open source JSF Components for building "rich" AJAXy interfaces, unfortunately makes Safari crash or hang or lock up with many of it's components.

I'm not the one who figured this out, but I wanted to document it here.

You can solve this the same way you solve the JSF/Google Ad issue:

Add this just before the tag on your .xhtml pages or template file:

<f:view contentType="text/html" />