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

Sortable and clickable lists with Apache Wicket

For my recent project I had to implement a sortable and clickable list. The project uses Apache Wicket as the front-end. It took me a while to get it all working and after some refactoring I was able to produce a generic solution to this problem. This solution is based on the following assumptions

  • The list to be sorted is based on a JPA entity and all the columns that need to be sortable are available as simple getter methods on the entity;
  • The entity has an id attribute, which uniquely identifies it;
  • The equals() and hasCode() methods of the entity will be overriden. Two entitiy instances will  be considered equal if their ids match;
  • The JavaScript jQuery library is available in the Wicket page displaying the list.

See the image below for an example of the eventual sortable and clickable list on which this blog is based.

The list shows the Account entity. All the columns are sortable and clicking on a row will open the url on which the hyperlink of the first column is based. There’s also a pagination link available in case the number of rows exceeds the number of rows displayed. In the remainder of this blog I’ll walk through all the necessary components.

Model

Interface ISortableEntity

For an entity to be able to be sorted in a list, I’ve defined the following interface:


package com.example.entity;

import java.math.BigDecimal;

public interface ISortableEntity {

 public BigDecimal getId();
 public void setId(BigDecimal id);
 public ISortableEntity newSearchInstance();

}

As you can see, the entity has to have an id attribute. It also has to implement the newSearchInstance() method which basically has to return a new instance of the entity class.

Entity Account

The list is based on an entity called com.example.entity.Account. The entity has to implement the ISortableEntity interface and has to override the equals() and hashCode() methods. As mentioned before two entities are considered equal if their ids match.

package com.example.entity;

import java.math.BigDecimal;
import javax.persistence.*;

@Entity
@Table(name = "ACCOUNTS")
@SequenceGenerator(allocationSize = 1, name = "account_id_generator", sequenceName = "accounts_id_seq")
public class Account implements java.io.Serializable, ISortableEntity {

	private static final long serialVersionUID = -1213397204916173215L;
	private BigDecimal id;
	private BigDecimal accountNumber;
	private String firstName;
	private String lastName;
	private String username;

	public Account() {
	}

	public Account(BigDecimal id) {
		this.id = id;
	}

	public Account(BigDecimal id, BigDecimal accountNumber, String firstName,
			String lastName, String username) {
		this.id = id;
		this.accountNumber = accountNumber;
		this.firstName = firstName;
		this.lastName = lastName;
		this.username = username;
	}

	@Id
	@GeneratedValue(generator = "account_id_generator")
	@Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0)
	public BigDecimal getId() {
		return this.id;
	}

	public void setId(BigDecimal id) {
		this.id = id;
	}

	@Column(name = "ACCOUNT_NUMBER", precision = 22, scale = 0)
	public BigDecimal getAccountNumber() {
		return this.accountNumber;
	}

	public void setAccountNumber(BigDecimal accountNumber) {
		this.accountNumber = accountNumber;
	}

	@Column(name = "FIRST_NAME", length = 100)
	public String getFirstName() {
		return this.firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	@Column(name = "LAST_NAME", length = 100)
	public String getLastName() {
		return this.lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Column(name = "USERNAME", length = 50)
	public String getUsername() {
		return this.username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Account other = (Account) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	public ISortableEntity newSearchInstance() {
		ISortableEntity searchAccount = new Account();
		return searchAccount;
	}
}

View

For the view layer the sortable list of accounts will be displayed on a Wicket Panel. For this we’ll need a template and the corresponding Java class. For the list to be sortable we first have to extend the abstract class org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider. For one of its methods, we also need to extend the abstract class org.apache.wicket.model.LoadableDetachableModel. I’ve generified both of these subclasses so they only depend on the aforementioned interface ISortableEntity. The code for these classes is shown below.

SortableEntityProvider

package com.example.view.common;

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;

import com.example.entity.ISortableEntity;

public class SortableEntityProvider<T extends ISortableEntity> extends
		SortableDataProvider<T> {
	private static final long serialVersionUID = -1646363525627999438L;
	private List<T> data;

	protected List<T> getData() {
		return data;
	}

	protected void setData(List<T> data) {
		this.data = data;
	}

	public SortableEntityProvider(List<T> data, String defaultSort) {
		super();
		setData(data);
		// set default sort
		setSort(defaultSort, true);
	}

	public Iterator<? extends T> iterator(int first, int count) {
		final SortParam sp = getSort();

		Collections.sort(getData(), getComparator(sp));

		return getData().subList(first, first + count).iterator();
	}

	public int size() {
		return getData().size();
	}

	public IModel<T> model(T object) {
		return new DetachableEntityModel<T>(object, getData());
	}

	protected Comparator<T> getComparator(final SortParam sp) {
		return new Comparator<T>() {
			public int compare(T arg0, T arg1) {

				int result;

				PropertyModel<Comparable> model0 = new PropertyModel<Comparable>(
						arg0, sp.getProperty());
				PropertyModel<Comparable> model1 = new PropertyModel<Comparable>(
						arg1, sp.getProperty());

				if (model0.getObject() == null && model1.getObject() == null) {
					result = 0;
				} else if (model0.getObject() == null) {
					result = -1;
				} else if (model1.getObject() == null) {
					result = 1;
				} else {
					result = model0.getObject().compareTo(model1.getObject());
				}

				if (!getSort().isAscending()) {
					result = -result;
				}

				return result;

			}
		};
	}
}

The constructor takes a list with all the rows displayed on the panel (usually a list of entities returned by a service calling a DAO) as the data parameter, along with a string representation of the attribute providing the default sort, eg. “firstName” for default sorting on the First Name of the Account.

DetachableEntityModel

package com.example.view.common;

import java.math.BigDecimal;
import java.util.List;

import org.apache.wicket.model.LoadableDetachableModel;

import com.example.entity.ISortableEntity;

public class DetachableEntityModel<T extends ISortableEntity> extends LoadableDetachableModel<T> {
	private static final long serialVersionUID = -2133620119433783150L;

	private final ISortableEntity entity;
	private final List<T> data;

	public DetachableEntityModel(ISortableEntity a, List<T> data) {
		this.entity = a;
		this.data = data;
	}

	public int hashCode() {
		return getId().hashCode();
	}

	public BigDecimal getId() {
		return entity.getId();
	}

	public boolean equals(final Object obj) {
		if (obj == this) {
			return true;
		} else if (obj == null) {
			return false;
		} else if (obj instanceof DetachableEntityModel) {
			DetachableEntityModel other = (DetachableEntityModel) obj;
			return other.getId() == getId();
		}
		return false;
	}

	protected T load() {
		ISortableEntity searchEntity = entity.newSearchInstance();
		searchEntity.setId(getId());
		return data.get(data.indexOf(searchEntity));
	}
}

AccountsPanel.html

<html>
<body>
<wicket:panel>
  <h2>Accounts</h2>
  <table class="clickable">
    <thead>
      <th wicket:id="orderByAccountNumber">[Account number]</th>
      <th wicket:id="orderByFirstName">[First name]</th>
      <th wicket:id="orderByLastName">[Last name]</th>
      <th wicket:id="orderByUsername">[Username]</th>
    </thead>
    <tbody>
      <tr wicket:id="accountRow">
        <td><a wicket:id="id" href="#"><span wicket:id="idSpan">[id]</span></a></td>
        <td wicket:id="firstName">[First name]</td>
        <td wicket:id="lastName">[Last name]</td>
        <td wicket:id="username">[Username]</td>
      </tr>
    </tbody>
  </table>
  <br />
  <span wicket:id="navigator">[dataview navigator]</span>
</wicket:panel>
</body>
</html>

The css class clickable will take care of the styling of the table and is coupled to the jQuery code (shown later on) that will implement the row clicks. A row click will make sure that the hyperlink coupled to the first column in the table will be clicked. The page also contains a component that will take care of the pagination.

AccountsPanel.java

package com.example.view.common;
....
public class AccountsPanel extends Panel {
 private static final long serialVersionUID = -395786600454932830L;

 @SpringBean
 AccountService accountService;

 public AccountsPanel(String id) {
 super(id);

 drawChildren();
 }

 private void drawChildren() {

 List<Account> accounts = accountService.getAccounts();

 SortableDataProvider<Account> dp = new SortableEntityProvider<Account>(
 accounts, "lastName");

 final DataView<Account> dataView = new DataView<Account>(
 "accountRow", dp) {
 private static final long serialVersionUID = 6364718764586838158L;

 @Override
 public void populateItem(final Item<Account> item) {
 item.add(new SimpleAttributeModifier("class",
 item.getIndex() % 2 == 0 ? "even" : "odd"));

 Link<Account> idLink = new Link<Account>("id") {
 private static final long serialVersionUID = -480222850475280108L;

 @Override
 public void onClick() {
 //TODO: Do stuff
 }
 };
 idLink.add(new Label("idSpan", item.getModelObject().getId()
 .toPlainString()));
 item.add(idLink);

 item.add(new Label("firstName", item.getModelObject()
 .getFirstName()));
 item.add(new Label("lastName", item.getModelObject()
 .getLastName()));
 item.add(new Label("username", item.getModelObject()
 .getUsername()));
 }
 };

 dataView.setItemsPerPage(5);

 addSorting(dp, dataView);

 add(dataView);

 add(new PagingNavigator("navigator", dataView));
 }

 private void addSorting(SortableDataProvider<Account> dp,
 final DataView<Account> dataView) {
 add(new SortableOrderBy<Account>("orderByAccountNumber", "id", dp,
 dataView));
 add(new SortableOrderBy<Account>("orderByLastName", "lastName", dp,
 dataView));
 add(new SortableOrderBy<Account>("orderByFirstName", "firstName", dp,
 dataView));
 add(new SortableOrderBy<Account>("orderByUsername", "username", dp,
 dataView));
 }

}
  • lines 6,7,17: Service (in this case a Spring Bean) that will cough up the list of accounts for display;
  • line 19,20: Here the SortableDataProvider is initialized with the accounts list. The accounts will be sorted on “lastName” by default;
  • line 22,23: The SortableDataProvider is fed to a DataView coupled to the accountRow table row wicket component;
  • line 36: This will be the code executed when the row is clicked;
  • line 52: The list displayed will contain 5 accounts;
  • line 54: Here the table row headers are added. They will be clickable for sorting the corresponding table column they belong to. I’ve created a small subclass for OrderByBorder, called SortableOrderBy so I don’t have to repeat the code that resets the pagination upon sort (the code will be provided below). Note that the second argument of the SortableOrderBy constructor is couple to the getter method on the Account entity. You can also apply sorting to properties of a related entity. Let’s presume that the Account entity has a relationship with an Address entity. If you would like to display say the city of the Address in the Accounts list and make it sortable, the second argument would be address.city ;
  • line 58: Here the paginator for the list is added to the panel.

SortableOrderBy.java

package com.example.view.common;

import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortStateLocator;
import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
import org.apache.wicket.markup.repeater.data.DataView;

public class SortableOrderBy<T> extends OrderByBorder {

  private static final long serialVersionUID = 5017011967951860645L;
  private final DataView<T> dataView;

  public SortableOrderBy(String id, String property,
      ISortStateLocator stateLocator, DataView<T> dataView) {
    super(id, property, stateLocator);
    this.dataView = dataView;
  }

  @Override
  protected void onSortChanged() {
    dataView.setCurrentPage(0);
  }
}

As I said, this is just a simple subclass to reset the current page to the first one after the sorting has been applied.

css

.clickable tr.even {
    background-color:#E9F9FF;
}

.clickable tr:hover {
    background-color: #F0F0F0;
}

.clickable td:hover {
    cursor: pointer;
}

I’m just showing the most important style elements. The above will make sure that:

  • the odd and even rows of the table are of different color;
  • the row that you hover over will change its color;
  • the arrow cursor will be replaced by a pointer cursor when you hover over a clickable row;

jQuery

The last piece of the puzzle is a small piece of jQuery code that will make sure the correct styling is applied and that a row click will lead to the clicking of the corresponding hyperlink programmatically.

$(document).ready(function() {

    $('.clickable tr').click(function() {
        var href = $(this).find("a").attr("href");
        if(href) {
            window.location = href;
        }
    });

})

And that’s all there is to it. With the code shown in this blog you can transform any list of entities into a sortable and clickable one.

%d bloggers like this: