Skip navigation

Introduction

I’ve been working dilligently lately on a new internal web application.  I made the decision long ago that the app would be a full blown AJAX application.  After deciding to go with DWR, Java and Hibernate running on Tomcat, I quickly noticed that my situation was pretty rare.  It seems as though everyone out there also uses Spring in combination with DWR and Hibernate.  I briefly went through the spring documentation and could not find a reason why I would need to use it for our application.

Along the way I’ve run into a few issues specifically with DWR and Hibernate and I’d like to share the experience for anyone else out there looking to do the same.

OpenSessionInViewFilter

I kept hearing about this magical filter for Spring that handles all of your Hibernate session management inside of AJAX application woes.  I was having all kinds of issues with sessions being closed, lazy loaded collections throwing exceptions etc.  The solution was this servlet filter:

public class OpenSessionInViewFilter implements Filter {

private static Log log = LogFactory.getLog(OpenSessionInViewFilter.class);

private SessionFactory sf;

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

try {
log.debug(“Starting a database transaction”);
Factory.beginTransaction();

// Call the next filter (continue request processing)
chain.doFilter(request, response);

// Commit and cleanup
log.debug(“Committing the database transaction”);
Factory.commitTransaction();

} catch (StaleObjectStateException staleEx) {
log
.error(“This interceptor does not implement optimistic concurrency control!”);
log
.error(“Your application will not work until you add compensation actions!”);
// Rollback, close everything, possibly compensate for any permanent
// changes
// during the conversation, and finally restart business
// conversation. Maybe
// give the user of the application a chance to merge some of his
// work with
// fresh data… what you do here depends on your applications
// design.
throw staleEx;
} catch (Throwable ex) {
// Rollback only
ex.printStackTrace();

// log the error
log.error(“Error in application”, ex);

try {
if (sf.getCurrentSession().getTransaction().isActive()) {
log.debug(“Trying to rollback database transaction after exception”);
Factory.rollbackTransaction();
}
} catch (Throwable rbEx) {
log.error(“Could not rollback transaction after exception!”,
rbEx);
}

// Let others handle it… maybe another interceptor for exceptions?
throw new ServletException(ex);
}
}

public void init(FilterConfig filterConfig) throws ServletException {
log.debug(“Initializing filter…”);
log.debug(“Obtaining SessionFactory from static HibernateUtil singleton”);
sf = Factory.getFactory();
}
}

It is very simple, it just opens up a transaction for each incoming request and either commits or rolls it back when all is finished.  The fact that it is a filter seems to be a key, I wrote a similar servlet that did not have the same effect.

DWR Converters

DWR comes with a number of converters that work really nicely.  The most interesting one happens to be the Hibernate3 converter.  It does a decent job.  There is one major issue with the converter though for my unique situation.  I have a number of data objects with properties that I do not want passed to the client.  For instance, any password property (for obvious reasons), image / blob properties etc.  DWR has a nice feature that you can use to exclude properties so they are not delivered to the client.  To use this feature you simply add the following to your objects convert node in dwr.xml:

<param name=”exclude” value=”properties, to, exclude” />

The only issue with this feature is it also excludes the property when transmitting data UP to the server.  So if you need to update one of these fields it is ignored on the way in.  Also, when you send the object to the server, DWR’s Hibernate3 converter starts with a fresh object.  This means, your properties that are excluded will be wiped out.  These are pretty serious problems that were not very easy to solve. The solution was to come up with my own converter which I named the H3SmartBeanConverter. This was adapted from a combination of the H3BeanConverter and the BasicObjectConverter which are a part of DWR.  You can download it below.

The first thing I had to change was the way the excludes are handled.  If the converter is performing an inbound conversion, all properties are converted regardless of the exclude rules:

// Access rules mean we might not want to do this one
// only check exclude rules when creating an outbound object, if write is required, allow everything to go through
if (!isAllowedByIncludeExcludeRules(name) && !writeRequired) {
continue;
}

I also needed lazy loaded properties to be initialized and passed down to the client as long as the property is not excluded.  To accomplish this I modified the section of code dealing with hibernate lazy properties:

if (readRequired) {
// This might be a lazy-collection so we need to double check
Object retval = method.invoke(example, new Object[] {});
if (!Hibernate.isInitialized(retval)) {
Hibernate.initialize(retval);
}
}

The final piece of the converter was the piece that fixes the problem of excluded properties being wiped out.  In the convertInbound method of the converter, I check to see if the object being converted extends my data object class.  If it does I load the object from the database prior to loading properties:

// if the bean is a data object, first load it from the database.  This will prevent properties that are not passed to the client from being deleted.
if (DataObject.class.isAssignableFrom(beanType)) {
// get the id field
String rawID = (String)tokens.get(“id”);
String[] split = ParseUtil.splitInbound(rawID);
String splitValue = split[LocalUtil.INBOUND_INDEX_VALUE];
String splitType = split[LocalUtil.INBOUND_INDEX_TYPE];

InboundVariable nested = new InboundVariable(iv.getLookup(), null, splitType, splitValue);
TypeHintContext incc = createTypeHintContext(inctx, (Property)properties.get(“id”));
Integer id = (Integer)converterManager.convertInbound(((Property)properties.get(“id”)).getPropertyType(), nested, inctx, incc);

if (id > 0) {
bean = Factory.get(beanType, id);
}
}

The Factory class is a basic wrapper for the Hibernate session object.  Factory.get calls Session.get under the covers.  Now, pre-loading the object from the database created yet another issue.  I was getting the error: “A collection with cascade=”alldeleteorphan” was no longer referenced by the owning entity.”  The problem here was once the object was loaded from the database, the collection properties were being overwritten with regular collection objects.  Hibernate uses specialized collection classes to handle persistence.  So, I had to add a bit of code to iterate through the collections instead of overwriting them:

// handle collections
if (Collection.class.isAssignableFrom(propType)) {
Collection collection = (Collection)property.getValue(bean);
collection.clear();

for (Object obj : (Collection)output) {
collection.add(obj);
}
} else if (Map.class.isAssignableFrom(propType)) {
Map map = (Map)property.getValue(bean);
map.clear();

for (Object obj : ((Map)output).entrySet()) {
Map.Entry mapEntry = (Map.Entry)obj;

map.put(mapEntry.getKey(), mapEntry.getValue());
}
} else {
property.setValue(bean, output);
}

Depending on which collections you are using you may need to add more code to handle them.  Finally, I had a working solution to my two biggest problems with DWR and Hibernate.  So far this solution has worked out really well, I have not run into any other issues.  If anyone has any suggestions as to how to do this in a simpler fashion, I’m all ears.

Download Java Classes:

H3SmartBeanConverter.java

OpenSessionInViewFilter.java

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: