6.2. Custom Value Types

6.2.1. Simple Types (ImmutableUserType)

Simple types are those that can be mapped using a single column (such as Color, above). For these we subclass from org.starobjects.jpa.applib.usertypes.ImmutableUserType. Here's the implementation of ColorType:

package org.starobjects.jpa.applib.usertypes;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.Hibernate;
import org.nakedobjects.applib.value.Color;

public class ColorType extends ImmutableUserType {

    public Object nullSafeGet(
            final ResultSet rs, 
            final String[] names, 
            final Object owner) throws SQLException {
        final int color = rs.getInt(names[0]);
        if (rs.wasNull()) {
            return null;
        }
        return new Color(color);
    }

    public void nullSafeSet(
            final PreparedStatement st, 
            final Object value, 
            final int index) throws SQLException {
        if (value == null) {
            st.setNull(index, Hibernate.INTEGER.sqlType());
        } else {
            st.setInt(index, ((Color) value).intValue());
        }
    }

    public Class<Color> returnedClass() {
        return Color.class;
    }

    public int[] sqlTypes() {
        return new int[] { Hibernate.INTEGER.sqlType() };
    }
}

The nullSafeGet() method is used to extract the value from the SQL ResultSet and instantiate the Color object. Conversely the nullSafeSet() method is used to read data from the provided Color and set up the SQL PreparedStatement so the value can be inserted or updated. The other two methods describe the structure of the data being read/written.

6.2.2. Composite Types (ImmutableCompositeUserType)

Composite types are those that are mapped using more than one column (such as Money, above). For these we subclass from org.starobjects.jpa.applib.usertypes.ImmutableCompositeType. Here's the implementation of MoneyType:

package org.starobjects.jpa.applib.usertypes;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.Hibernate;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;
import org.nakedobjects.applib.value.Money;

public class MoneyType extends ImmutableCompositeType {

    public Class<Money> returnedClass() {
        return Money.class;
    }

    public Object nullSafeGet(
            final ResultSet resultSet,
            final String[] names,
            final SessionImplementor session,
            final Object owner) throws SQLException {
        final BigDecimal amount = resultSet.getBigDecimal(names[0]);
        if (resultSet.wasNull()) {
            return null;
        }
        final String currency = resultSet.getString(names[1]);
        return new Money(amount.doubleValue(), currency);
    }

    public void nullSafeSet(
            final PreparedStatement statement,
            final Object value,
            final int index,
            final SessionImplementor session) throws SQLException {
        if (value == null) {
            statement.setNull(index, Hibernate.BIG_DECIMAL.sqlType());
            statement.setNull(index + 1, Hibernate.STRING.sqlType());
        } else {
            final Money amount = (Money) value;
            statement.setBigDecimal(index, amount.getAmount());
            statement.setString(index + 1, amount.getCurrency());
        }
    }

    public String[] getPropertyNames() {
        return new String[] { "amount", "currency" };
    }

    public Type[] getPropertyTypes() {
        return new Type[] { Hibernate.BIG_DECIMAL, Hibernate.STRING };
    }

    public Object getPropertyValue(final Object component, final int property) {
        final Money monetaryAmount = (Money) component;
        if (property == 0) {
            return monetaryAmount.getAmount();
        } else {
            return monetaryAmount.getCurrency();
        }
    }

    public void setPropertyValue(final Object component, final int property, final Object value) {
        throw new UnsupportedOperationException("Money is immutable");
    }
}

This works in broadly the same way, with nullSafeGet() and nullSafeSet() used to read values from SQL/write values to SQL. The getPropertyNames() and getPropertyTypes() again describe the structure of the value. The getPropertyValue() allow specific properties of the value (such as the Money's currency) to be read; like reading a single column in the database. The setPropertyValue() meanwhile should always thrown an exception because value types should be immutable (replaced in their entirety rather than modified in-situ).