Binding enum fields with i8n localization support

29 Mar

It’s easy to argue that enums are a good thing.

Typically, when developing an application, you’re bound to run into a collectible field that has a finite number of selections. Chances are you’re already maintaining a legacy application, or developing for a legacy data model that is chock-full of this type of field; characterized by a cryptic and garbled mess of char(1) values that look like FAVORITE_ICECREAM: 1=”chocolate”, b=”mint chip”, Z=”strawberry”.

But you’re not faced with only the problem of selection and storage, but also comparison: and not just in java code, but likely in the view layer (for example when conditionally rendering portions of a form or view). This is where enums shine: your fellow developers are going to hate you less when they can see “${flavor eq ‘CHOCOLATE’}” rather than “${flavor.charValue == 28}”.

Yet, enums are not without their own headaches. Particularly when trying to find a clean way to expose them to JSP, and bind an enumerated selection to a field with spring. It’s not for lack of trying: spring does have some basic support for binding enum with spring:options. However, the missing piece that throws a wrench in the proverbial gears for many is the lack of support for i8n localization. This is a feature which has been noticeably pushed back from version-to-version for quite a while. That’s not to say there haven’t been some interesting workarounds. But, what I’ve seen so far has been less than clean in implementation.

We’ll look for cleaner solution by sticking to a couple principles. First, our problem is in the view layer, thus our solution should be in the view layer: we don’t want a mess in our application layer to correct our form binding and message display. Second, Spring already provides us with some useful form binding tags. The form:select and form:option tags work fine, and I generally hate form:bind, so we’ll reuse these.

Luckily we can adhere to our zealotry by implementing a pretty simple .tag file. Our goal is very close to a standard spring tag; something that looks like this: <customTags:enumSelector path=”myEnumField”/>
Ideally, this tag should generate a drop down of message-file-translated options, in the order they appear in the enum. When one is selected and the form is submitted, spring should bind the selected enum value into the myEnumField field.

Anticlimactically, here’s the .tag that accomplishes this:

<%@ taglib uri="" prefix="c" %>
<%@ taglib uri="" prefix="form"%>
<%@ taglib uri="" prefix="spring"%>
<%@ attribute name="path" type="java.lang.Object" rtexprvalue="true" required="true" %>

<!-- Identify the enumeration class identified by the subBean path.
Do this using a BeanWrapper, in case the field containing the enum value is null. -->
expression="'${nestedPath}'.substring(0, '${nestedPath}'.length() - 1)"
<spring:eval expression="${basePath}" var="baseBean"/>
<spring:eval expression="new org.springframework.beans.BeanWrapperImpl(baseBean)"
<spring:eval expression="beanWrapper.getPropertyType('${path}')" var="pathClass"/>
<!-- Create a select option for each of the enum constants. -->
<form:select path="${path}">
<c:forEach items="${pathClass.enumConstants}" var="enumConst">
<!-- Value of option is the enum constant name, which spring knows
how to bind using enum.valueOf(). -->
<form:option value="${enumConst}">
<!-- Label is pulled from propfile as simpleName.constant,
e.g Color.RED -->
<spring:message code="${pathClass.simpleName}.${enumConst}"/>

There’s not a lot of magic going on here, so to break it down:

We pass in one attribute: path. It works just like a spring path.
Path on what bean, though? Well, our model object is chilling out there, and luckily spring form has exposed the name of the form model attribute into the attribute named “nestedPath”. We can take a hint from the existing spring form tags and unwrap that variable. The spring:eval tag is especially helpful for this, since it lets us dereference EL and then use that inside of another EL statement, as well as do some substring manipulation to get rid of the unruly trailing period.

Next we need to introspect the enum values from the path. This is trivial once we have the class of the field, because Class.getEnumConstants() will happily give us the whole enumeration. But we have to get the class first…
The catch is that we can’t just do like <spring:eval expression=”${path}.class”/> because the value at the end of path could be null, and checking the class wouldn’t be a null safe operation. But, spring’s beanWrapper is very handy here. We can utilize it to get the property class of the potentially nested field described by “path” with no headaches.

From there, it’s a simple <c:forEach> to write out form:option tags, with some spring:message code format of your choosing, and a bind value of the enum constant’s name, which spring already has a converter for.

Still here? What, you want a mavenized example project?
Sure, that’s right here:

No comments yet

Leave a Reply

Your email address will not be published.