Thursday, April 19, 2007

Stinky Baby

Since I moved my family away from our home in Madison last summer, I haven't had a centralized media server for things like images and music. In fact, I work almost entirely from a laptop these days. The simplicity has it's advantages, but among the disadvantages is that all of the photos that Shana takes of Gideon get downloaded to her computer, where it takes an extra effort to dig out and browse through the image collection.

What this means to you? Probably not much -- but I mention it to give an excuse for why I don't post more pictures of my firstborn.

So, here they are. The shirt is one we bought at Murray's Cheese shop in New York City on a trip that Shana and I took while Gideon was still in Utero. I didn't think it would fit until he was well past his first year of life, such as it is marked. However, at 11 months and some days, it fits quite well. Perhaps that also serves to illustrate how well he has been growing.

Regardless, having a shirt declaring an adjective synonymous with his last name is funny. Especially since we all now know that it's about the cheese we adore and miss so much from Wisconsin.

In this shot, I think he resembles Shana quite a bit.



Here, does he resemble me? I'll let you be the judge ,'-]

Thursday, April 12, 2007

I know why a Struts 2 File Upload fails

Yesterday I began some work on using Struts2 to do a file upload. The way it works is pretty slick. At least when it works.

The problem was that when uploading files, I'd be surprised with null values. However, when pressing the back button and re-submitting the form, it would work fine. Odd behavior indeed, but I'm not alone. Other people have had the same problem uploading files with Struts 2.0.6.

I dug into it a little bit. To quite a deep level actually, and I found the bug. The good news is that it seems to be related specifically to Struts 2.0.6. There's a point in the Filter, which serves as the starting point of the whole framework, where the HttpServletRequest is supposed to be wrapped by the appropriate class for later data retrieval.

In the case of the mysterious broken file upload, the interceptor (FileUploadInterceptor) that handles populating the struts action with the values checks to see if the Request object is an instance of the MultiPartRequestWrapper. If it is, the interceptor attempts to pull out the multipart data; that is, the actual file bytes. If it's not, it does nothing. When the file uploads don't work it means that the Request has not been wrapped in the MultiPartRequestWrapper. Therefore the FileUploadFilter doesn't do anything, even though the data is actually there.

The reason why it doesn't get wrapped relates to the FilterDispatcher (org.apache.struts2.dispatcher package) and the prepareDispatcherAndWrapRequest method. In that method, there's a logic block which deals with the Dispatcher instance. When the instance is null, it sets the Dispatcher instance to one that will work. The peculiar thing about this block, is that the part of the method that does the request wrapping is also in that logic block. Therefore, the request doesn't get wrapped in the MultiPartRequestWrapper unless the Dispatcher instance is null. When the file uploads failed, this instance was NOT null, causing the Request to push through as the RequestFacade. The FileUploadInterceptor doesn't work with that data type.

The fix? Just put the code that does the wrapping outside of the logic block that deals with the dispatcher instance.

To the Struts team's credit, it looks like this bug has been fixed. The code in the repository after the 2.0.6 tag does the wrapping correctly.

Here's some code for those who care.

First, the code that comes with Struts 2.0.6

    protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {

Dispatcher du = Dispatcher.getInstance();

// Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be
// configured first before struts2 dispatcher filter, hence when its cleanup filter's turn,
// static instance of Dispatcher should be null.
if (du == null) {

Dispatcher.setInstance(dispatcher);

// prepare the request no matter what - this ensures that the proper character encoding
// is used before invoking the mapper (see WW-9127)
dispatcher.prepare(request, response);

try {
// Wrap request first, just in case it is multipart/form-data
// parameters might not be accessible through before encoding (ww-1278)
request = dispatcher.wrapRequest(request, getServletContext());
} catch (IOException e) {
String message = "Could not wrap servlet request with MultipartRequestWrapper!";
LOG.error(message, e);
throw new ServletException(message, e);
}
}
else {
dispatcher = du;
}
return request;
}

Code that works:


    protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {

Dispatcher du = Dispatcher.getInstance();

// Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be
// configured first before struts2 dispatcher filter, hence when its cleanup filter's turn,
// static instance of Dispatcher should be null.
if (du == null) {

Dispatcher.setInstance(dispatcher);

// prepare the request no matter what - this ensures that the proper character encoding
// is used before invoking the mapper (see WW-9127)
dispatcher.prepare(request, response);

}
else {
dispatcher = du;
}


//Note that this wrapping code is where it should be
try {
// Wrap request first, just in case it is multipart/form-data
// parameters might not be accessible through before encoding (ww-1278)
request = dispatcher.wrapRequest(request, getServletContext());
} catch (IOException e) {
String message = "Could not wrap servlet request with MultipartRequestWrapper!";
LOG.error(message, e);
throw new ServletException(message, e);
}
return request;
}