Passing null to the model in JSF
The problem
JSF allows you to bind an input component to a model bean via a so-called value binding. A notorious major headache is that a null is automatically coerced into a zero (0) when the bounded property is of type Integer or Long. This behavior only seems to rarely be the required one. A number of other types are coerced as well, e.g. in case of Boolean it's coerced to Boolean.FALSE.
Finding a solution
Although coercing is a literal interpretation of the spec, it seems that basically only Tomcat that implements this somewhat peculiar behavior. They have a setting to turn this off:
-Dorg.apache.el.parser.COERCE_TO_ZERO=falseSee e.g. jsf: integer property binded to a inputtext in UI is set to zero on submit
If you're in a position to use this option, don't read further and use it.
If you can't use this option (you have e.g. a big system, lots of testing needed) and a null is needed at only a few places, another option is required. One possibility, that I will describe here, is based on a value-changed listener and a custom tag handler. What we do is breaking up the existing value binding of the input component in a base part and a property part, and then setting a null directly via the Apache BeanUtils library.
The following shows a TagHandler implementation that does this breaking up and installs a value-changed listener on the parent component:
public class MinusOneToNullConverter extends TagHandler { private static final Class<?>[] VALUECHANGE_LISTENER_ZEROARG_SIG = new Class[] {}; private static final String LISTENER_EL = "#{minusOneToNullListener.processValueChange(component, %s, '%s')}"; public MinusOneToNullConverter(TagConfig config) { super(config); } @Override public void apply(FaceletContext ctx, UIComponent parent) throws IOException { String expression = parent.getValueExpression("value").getExpressionString(); if (expression.contains(".")) { String base = expression.substring(2, expression.lastIndexOf('.')); String property = expression.substring(expression.lastIndexOf('.') + 1, expression.length() - 1); ExpressionFactory expressionFactory = ctx.getExpressionFactory(); ((EditableValueHolder) parent).addValueChangeListener( new MethodExpressionValueChangeListener(expressionFactory.createMethodExpression(ctx, String.format(LISTENER_EL, base, property), Void.class, VALUECHANGE_LISTENER_ZEROARG_SIG) )); } } }
Note that the EL expression uses 'component' as the first parameter for the processValueChange method. This is an implicit EL object that refers to the current component being processed, which is in the case of this tag the parent component. Implementation wise it's important to use the FaceletContext as the ELContext, instead of grabbing it from the FacesContext, when creating the method expression. Otherwise the context of the method expression would not be entirely correct and things like <ui:param>s will not be resolved.
The following shows the value-changed listener that will be installed:
@Named public class MinusOneToNullListener { public void processValueChange(UIInput component, Object object, String property) throws AbortProcessingException { if (component.getValue() != null && component.getValue().toString().equals("-1")) { component.resetValue(); try { PropertyUtils.setProperty(object, property, null); } catch (IllegalAccessException e) { throw new AbortProcessingException(e); } catch (InvocationTargetException e) { throw new AbortProcessingException(e); } catch (NoSuchMethodException e) { throw new AbortProcessingException(e); } } } }
As can be seen, the listener uses the input component to check if the value submitted was "-1". The string representation is used here to be compatible with different types, although there are of course some other possibilities here. The listener will then reset the value of the component, so JSF will not attempt later on to push the -1 to our model object. Finally, bean utils is used to set the actual null value.
In this example, we used -1 to encode the desire for null. Other options might be feasible as well, such as the empty string or perhaps even null itself.
Before using this all, there is the tedious but mandatory registration of the tag handler in a *-taglib.xml file:
<tag> <tag-name>minusOneToNullConverter</tag-name> <handler-class>com.example.MinusOneToNullConverter</handler-class> </tag>
Using the 'converter'
After doing the above, we're now ready to use this on a Facelet, e.g. :<h:selectOneMenu value="#{someBean.value}"> <my:minusOneToNullConverter /> <f:selectItem itemLabel="ALL" itemValue="-1" /> <f:selectItems value="#{data.somethings}" var="something" itemLabel="#{something.label}" itemValue="#{something.key}" /> </h:selectOneMenu>
Any other alternatives?
Once again, if on Tomcat, -Dorg.apache.el.parser.COERCE_TO_ZERO=false should be your first choice. If you use an application server that isn't based on Tomcat, you also probably don't need this. There's a spec proposal open that asks to change this behavior, but despite a large amount of votes it hasn't seen any activity for a long time.
Hopefully the hacky workaround presented here is helpful to someone.
Arjan Tijms
Comments
Post a Comment