Using a nested property model in Apache Wicket for money validation

For one of my recent projects i needed to make sure that money fields were correctly translated from screen to database and vice versa.

The money fields are implemented as BigDecimal properties in the JPA model. The default Wicket behavior on input is to allow both the “.” and “,” characters and according to the active Locale to ignore the one that doesn’t correspond to the decimal seperator. This means that when the active Locale is the Dutch one (which uses the comma as the decimal seperator) and a user accidently enters €12.35 as the amount. It gets stored as €1235. This is very dangerous behavior.

The way we wanted the application to behave is to validate the input according to the Session Local and to disallow the entering of the wrong decimal seperator. There are probably a couple of way to do this, but the one i used involved using a custom validator and a nested property model.

Nested property model

The nested property model nests a BigDecimal model into a String model. Here’s the code.

package org.example.view.common;

import java.math.BigDecimal;
import org.apache.wicket.model.IModel;
import org.example.util.BigDecimalParser;
import org.example.view.format.BigDecimalToMoneySessionFormat;

public class DefaultMoneyModel implements IModel {

  private static final long serialVersionUID = 1479000955482442517L;
  private final IModel mNestedModel;

  public DefaultMoneyModel(IModel nestedModel) {
    mNestedModel = nestedModel;
  }

  public String getObject() {
    BigDecimal value = mNestedModel.getObject();
    // convert BigDecimal to String
    return BigDecimalToMoneySessionFormat.format(value);
  }

  public void setObject(String object) {
    // convert String to Bigdecimal
    BigDecimal value = BigDecimalParser.parseBigDecimal(object, getSessionLocale());
    mNestedModel.setObject(value);
  }

  public void detach() {
    mNestedModel.detach();
  }

  private Locale getSessionLocale() {
    // code to get the current Session Locale, omitted for brevity
    ....
  }
}

Custom validator

The custom validator i wrote first tries to parse the String to a BigDecimal just like Apache Wicket does and adds an extra check for the decimal separator.

package com.redwood.exchange.view.validator;

import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.validator.AbstractValidator;
import org.example.util.BigDecimalParser;

public class BigDecimalMoneyValidator extends AbstractValidator {

  private static final long serialVersionUID = 5638024896256902410L;

  @Override
  protected void onValidate(IValidatable validatable) {
    try {
      // First check that a normal conversion works
      @SuppressWarnings("unused")
      BigDecimal bigDecimal = BigDecimalParser.parseBigDecimal(
        validatable.getValue(), getSessionLocale());

      // Now check if the only non digit character is the decimal sign
      // corresponding to the locale and that it appears only once.
      checkDecimalSeparator(validatable.getValue(), getSessionLocale());
    } catch (Exception e) {
      error(validatable);
    }
  }

  private void checkDecimalSeparator(String value, Locale sessionLocale) {
    char decimalSeperator = new DecimalFormatSymbols(sessionLocale)
      .getDecimalSeparator();

    Pattern p = Pattern
      .compile("^\\d+(\\" + decimalSeperator + "\\d{2})?$");
    Matcher m = p.matcher(value);
    if (!m.find()) {
      throw new NumberFormatException("Invalid price.");
    }
  }

  private Locale getSessionLocale() {
    // code to get the current Session Locale, omitted for brevity
    ....
  }
}

Utility classes and resources

I need a parser class for parsing a String, i.e. user input, to a BigDecimal and a formatter class for presentation of the BigDecimal as a money field on the web page. Both should take the current Locale into account. Here’s the code for both classes.

package com.redwood.exchange.util;

import java.math.BigDecimal;
import java.util.Locale;
import org.apache.wicket.util.convert.converters.BigDecimalConverter;

public class BigDecimalParser {

  public static BigDecimal parseBigDecimal(String s, Locale locale) {
    BigDecimalConverter converter = new BigDecimalConverter();
    BigDecimal bigDecimal = (BigDecimal) converter.convertToObject(s,
      locale);
    return bigDecimal;
  }
}
package org.example.view.format;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

public class BigDecimalToMoneySessionFormat {

  public static String format(BigDecimal decimal, Locale locale) {
    if (decimal == null) {
      return "";
    }
    else {
      DecimalFormat decimalFormat = new DecimalFormat();
      decimalFormat.setMinimumFractionDigits(2);
      decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols
        .getInstance(locale));
      return decimalFormat.format(decimal);
    }
  }
}

Finally for displaying an error message i added a WicketApplication.properties file on the org.example.view level

BigDecimalMoneyValidator=The entered value is not a valid ${label}.

Adding it all to a Wicket Panel

Now that we have all the necessary code, let’s use it on a Wicket panel. Assume that we have a JPA entity class named Order with a property called price, which is a BigDecimal.

entering money

The money field is put on a html page corresponding to a Wicket panel via the following snippet:

  <span><div wicket:id="price" /></span>

The following code snippet from the corresponding Wicket Panel class will couple the component to the nested model and add the custom validator.

  TextField<String> priceField = new TextField<String>(
    "price", new DefaultMoneyModel(new PropertyModel<BigDecimal>(
    order, "price")));
  priceField.add(new BigDecimalMoneyValidator());

displaying money

The money field is put on a html page corresponding to a Wicket panel via the following snippet (in this case it’s put in a html table):

  <td wicket:id="price">EUR 12.95</td>

Now the following code in the corresponding Wicket panel class will couple the component to the nested model for use in a DataView.

  final DataView<Order> dataView = new DataView<Order>(..,..) {

    private static final long serialVersionUID = -6981777123077670308L;

    @Override
    public void populateItem(final Item<JPAEntity> item) {
      ..
      item.add(new Label("price", BigDecimalToMoneySessionFormat.format(item
        .getModelObject().getPrice())));
    }
  };

More info

For more info on using nested property models check out this excellent blog by Jeremy Tomerson.

Advertisements

About Roger Goossens
I'm an integration consultant with a strong affiliation for JEE and open source development.

2 Responses to Using a nested property model in Apache Wicket for money validation

  1. Matt Darwin says:

    Couldn’t you just do
    String pattern = “^\\d+(\\” + decimalSeperator + “\\d{2})?$”;
    priceField.add (new PatternValidator(pattern));
    ?

    • Hey Matt,

      It’s a long time ago I wrote this post. But to answer your question: i suppose you could do it your way. But then you only have the validation portion covered. If you use my approach, you also take care of formatting the money fields for display.
      Apart from that I think my validation solution is a little more DRY and it clearly shows that we’re dealing with a Money field. But maybe that’s a matter of personal taste.

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: