Add a new model type: Joda time

The idea is to add some new model types in a jspresso project with a minimal effort. Also:

- the types are simple ones, i.e. with only one value to deal with,

- and there would be no need to work at the UI level.

Luckily I only need to use the joda types LocalDate and LocalDateTime in a jspresso project. This use case shows how to implement such simple types. With more complex type like Joda Interval, it'd have been an other story!

To do such a thing we need to deal with five issues:

  1. To create new model descripors, one for each new type, here ILocalDatePropertyDescriptor and ILocalDateTimePropertyDescriptor.
  2. To create specific connectors:
    •     for Swing and Wings, with dedicated ones: JJodaTimeFieldConnector and XJodaTimeConnector,
    •     for others, with a common factory: JodaRemoteConnectorFactory, which generates JodaRemoteValueConnector instances.
  3. To manage these connectors in a new view factory for each UI. These factories will use a new format adapter for each model type.
    And they must be specified in the frontend configuration.
  4. To make  the model configuration "joda aware" by changing the template HibernateXdocletEntity.ftl.
  5. To configure hibernate to manage the joda types.

Now the new types can be used in place of standard date, with the same syntax and the same attributes. To do it, just specify the new interfaces in the model configuration file, e.g.:

  <bean 
    id="dateType"
    class="org.popsuite.framework.model.descriptor.basic.BasicLocalDatePropertyDescriptor"
    abstract="true" />
[...]
  <bean parent="dateType">
     <property name="name"
                    value="paymentDay" />
  </bean>

To be used without limitation!

The dedicated model descriptors

A model descriptor is agnostic in regard of the UI technology. Each new model type needs only one descriptor. At the moment, only Joda Date and DateTime are implemented.

For each type we need an interface and at least one implementation. There is no need to deal with UI (until we want to enhance the current presentation of a date...). So the new interfaces and implementations are simply derived from the current date ones.

First the two interfaces:

package org.popsuite.framework.model.descriptor;
import org.jspresso.framework.model.descriptor.IDatePropertyDescriptor;

public interface ILocalDateTimePropertyDescriptor extends IDatePropertyDescriptor {}

and

package org.popsuite.framework.model.descriptor;

public interface ILocalDatePropertyDescriptor extends ILocalDateTimePropertyDescriptor {}

Then an implementation:

package org.popsuite.framework.model.descriptor.basic;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.model.descriptor.EDateType;
import org.jspresso.framework.model.descriptor.basic.BasicDatePropertyDescriptor;
import org.popsuite.framework.model.descriptor.ILocalDateTimePropertyDescriptor;

public class BasicLocalDateTimePropertyDescriptor extends BasicDatePropertyDescriptor
implements ILocalDateTimePropertyDescriptor {

    public BasicLocalDateTimePropertyDescriptor() {
        super();
    }

    @Override  
    public Class<?> getModelType() {
        return LocalDateTime.class;
    }

    @Override
    public EDateType getType() {
        if (super.getType() != null) {
            return super.getType();
        }
        return null;
    }

}

and

package org.popsuite.framework.model.descriptor.basic;
import org.joda.time.LocalDate;
import org.jspresso.framework.model.descriptor.EDateType;
import org.popsuite.framework.model.descriptor.ILocalDatePropertyDescriptor;

public class BasicLocalDatePropertyDescriptor extends BasicLocalDateTimePropertyDescriptor
implements ILocalDatePropertyDescriptor {

    public BasicLocalDatePropertyDescriptor() {}

    @Override  
    public Class<?> getModelType() {
        return LocalDate.class;
    }

    @Override
    public EDateType getType() {
        return EDateType.DATE;
    }

}

The connectors

Only the Swing and Wings versions of jspresso need the creation of new dedicated connectors. For the others remote UI, Flex and Qooxdoo, a connector factory is used.

Swing

package org.popsuite.framework.binding.swing;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.text.ParseException;
import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.binding.ConnectorBindingException;
import org.jspresso.framework.binding.swing.JDateFieldConnector;
import org.jspresso.framework.gui.swing.components.JDateField;

/**
 * JJodaTimeField connector.
 *
 * Manages LocalDate and LocalDateTime values.
 *
 */
public class JJodaTimeFieldConnector extends JDateFieldConnector {

    /**
     * Constructs a new <code>JDateField</code> instance.
     *
     * @param id
     *            the connector identifier.
     * @param dateField
     *            the connected JDateField.
     */
    public JJodaTimeFieldConnector(String id, JDateField dateField) {
        super(id, dateField);
    }

    /**
     * Will be called by <code>setConnecteeValue</code>.
     *
     * {@inheritDoc}
     */
    @Override
    protected void protectedSetConnecteeValue(Object aValue) {
        Object result = aValue;
        if (aValue instanceof LocalDateTime) {
            result = isUnknown(aValue) ? null : ((LocalDateTime) aValue).toDateTime().toDate();       
        } else if (aValue instanceof LocalDate) {
            result = isUnknown(aValue) ? null : ((LocalDate) aValue).toDateTimeAtStartOfDay().toDate();
        }
        super.protectedSetConnecteeValue(result);
    }

    /**
     * Gets the value out of the connector text after parsing the string
     * representation.
     * <p>
     * {@inheritDoc}
     */
    @Override
    protected Object getConnecteeValue() {
        // don't call getValue() due to bad focus event delivery order of
        // JFormattedTextField.
        // return getConnectedJComponent().getValue();
        try {
            if (getConnectedJComponent().getFormattedTextField().getText() = = null
                    || getConnectedJComponent().getFormattedTextField().getText().length() = = 0) {
                return null;
            }

            Object result = getConnectedJComponent().getFormattedTextField().getFormatter()
                .stringToValue(getConnectedJComponent().getFormattedTextField().getText());
            Class<?> expectedType = null;
            if (getModelConnector().getModelDescriptor() != null) {
                expectedType = getModelConnector().getModelDescriptor().getModelType();
            } else {
                expectedType = getModelDescriptor().getModelType();
            }
            return (LocalDateTime.class.equals(expectedType))
            ? LocalDateTime.fromDateFields((Date) result)
                    : LocalDate.fromDateFields((Date) result);
        } catch (ParseException ex) {
            throw new ConnectorBindingException(ex);
        }
    }

}

Wings

package org.popsuite.framework.binding.wings;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.binding.wings.XCalendarConnector;
import org.wingx.XCalendar;

public class XJodaTimeConnector extends XCalendarConnector{

    public XJodaTimeConnector(String id, XCalendar dateField) {
        super(id, dateField);
    }

    /**
     * Sets the value to the connector text after formatting the string
     * representation.
     * <p>
     * {@inheritDoc}
     */
    @Override
    public void setConnecteeValue(Object aValue) {
        Date result = null;
        if (aValue instanceof LocalDate) {
            result = isUnknown(aValue) ? null : ((LocalDate) aValue).toDateTimeAtStartOfDay().toDate();
        }
        else if(aValue instanceof LocalDateTime) {
            result = isUnknown(aValue) ? null : ((LocalDateTime) aValue).toDateTime().toDate();
        }
        else {
            result = (Date) aValue;
        }
        getConnectedSComponent().setDate(result);
    }

    /**
     * Gets the value out of the connector text after parsing the string
     * representation.
     * <p>
     * {@inheritDoc}
     */
    @Override
    protected Object getConnecteeValue() {
        Object result =  getConnectedSComponent().getDate();
        if (null = = result) return null;

        Class<?> expectedType = null;
        if (getModelConnector().getModelDescriptor() != null) {
            expectedType = getModelConnector().getModelDescriptor().getModelType();
        } else {
            expectedType = getModelDescriptor().getModelType();
        }
        result = (LocalDateTime.class.equals(expectedType)) 
        ? LocalDateTime.fromDateFields((Date) result)
                : LocalDate.fromDateFields((Date) result);
        return result;
    }

}

Flex and Qooxdoo

A connector factory is used with the two other remote UI, Flex and Qooxdoo. This factory generates JodaValueConnectors.

The factory:

package org.popsuite.framework.binding.remote;

import org.jspresso.framework.binding.IValueConnector;
import org.jspresso.framework.binding.remote.RemoteConnectorFactory;
import org.jspresso.framework.binding.remote.RemoteValueConnector;

public class JodaRemoteConnectorFactory extends RemoteConnectorFactory{

    @Override
    public IValueConnector createValueConnector(String id) {
        RemoteValueConnector connector = new JodaRemoteValueConnector(id, this);
        attachListeners(connector);
        return connector;
    }

}

And the value connector:

package org.popsuite.framework.binding.remote;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.binding.remote.RemoteConnectorFactory;
import org.jspresso.framework.binding.remote.RemoteValueConnector;

public class JodaRemoteValueConnector extends RemoteValueConnector{

    public JodaRemoteValueConnector(String id,
            RemoteConnectorFactory connectorFactory) {
        super(id, connectorFactory);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object getValueForState() {
        Object result = super.getValueForState();
        if (result instanceof LocalDate) {
            result = isUnknown(result) ? null : ((LocalDate) result).toDateTimeAtStartOfDay().toDate();
        }
        else if(result instanceof LocalDateTime) {
            result = isUnknown(result) ? null : ((LocalDateTime) result).toDateTime().toDate();
        }
        return result;
    }   

    /**
     * {@inheritDoc}
     */
    @Override
    protected void setConnecteeValue(Object connecteeValue) {
        Object result = connecteeValue;
        if (result instanceof Date)
        {
            Class<?> expectedType = getModelConnector().getModelDescriptor().getModelType();
            if (LocalDateTime.class.equals(expectedType)) {
                result = LocalDateTime.fromDateFields((Date) result);
            } else if (LocalDate.class.equals(expectedType)) {
                result = LocalDate.fromDateFields((Date) result);
            }
        }
        super.setConnecteeValue(result);
    }

}

Special case: unknow dates

The framework popsuite defined unknown values for the dates. Such values must be intercepted by the connectors (see above).

Note: a use case of how to implement unknown values with jspresso will be the topic of a future wiki entry. Cool

package org.popsuite.framework.model.specialcase;

import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;

public class SpecialDates {
   
    public static final LocalDate UNKNOWN_DATE  =
        new LocalDate().year().setCopy(new LocalDate().year().getMinimumValueOverall())
                       .dayOfYear().withMaximumValue();
    public static final LocalDate UNKNOWN_BEGIN = UNKNOWN_DATE;
    public static final LocalDate UNKNOWN_END   =
        new LocalDate().year().setCopy(new LocalDate().year().getMaximumValueOverall())
                       .dayOfYear().withMinimumValue();
   
    public static boolean isUnknown(Object date)
    {
        if (null = = date) return true;
        if (date instanceof Date) return false;
       
        if(date instanceof LocalDate) {
            return ! UNKNOWN_BEGIN.isBefore( (LocalDate) date) || ! UNKNOWN_END.isAfter( (LocalDate) date) ;
        }
        else if (date instanceof LocalDateTime) {
            return ! (new LocalDateTime(UNKNOWN_BEGIN)).isBefore( (LocalDateTime) date)
                   || ! (new LocalDateTime(UNKNOWN_END)).isAfter( (LocalDateTime) date) ;
        }
        return true;
    }
}

The View factories

Each UI needs a specific view factory, but Flex and Qooxdoo share the same one. These factories are derived from the standard ones. The former must be used in placed of the latter in the frontend configuration file.

Swing

package org.popsuite.framework.view.swing;
[...]
/**
 * Enhanced view factory:
 * - manages the joda's dates
 *
 */
public class PopsuiteSwingViewFactory extends DefaultSwingViewFactory {

    private static final Date                  TEMPLATE_DATE               = new Date(27166271000L);
         
    @Override
    protected IView<JComponent> createPropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IView<JComponent> view = null;
        IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            view = createLocalDateTimePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        if (view = = null) {
            return super.createPropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        decorateWithDescription(propertyDescriptor, locale, view); // see AbstractViewFactory.createPropertyView
        return view;
    }

    protected TableCellRenderer createTableCellRenderer(IPropertyDescriptor propertyDescriptor, Locale locale) {

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            return createDateTableCellRenderer(
                    (ILocalDatePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            return createDateTableCellRenderer(
                    (ILocalDateTimePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        return super.createTableCellRenderer(propertyDescriptor, locale);
    }

    protected IView<JComponent> createLocalDatePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDatePropertyDescriptor propertyDescriptor = (ILocalDatePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        JDateField viewComponent = createJDateField(locale);
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.getFormattedTextField().setFormatterFactory(
                new DefaultFormatterFactory(new DateFormatter(format)));
        JJodaTimeFieldConnector connector = new JJodaTimeFieldConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                Toolkit.getDefaultToolkit().getScreenResolution() / 3);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    protected IView<JComponent> createLocalDateTimePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDateTimePropertyDescriptor propertyDescriptor = (ILocalDateTimePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        JDateField viewComponent = createJDateField(locale);
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.getFormattedTextField().setFormatterFactory(
                new DefaultFormatterFactory(new DateFormatter(format)));
        JJodaTimeFieldConnector connector = new JJodaTimeFieldConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                Toolkit.getDefaultToolkit().getScreenResolution() / 3);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    private TableCellRenderer createDateTableCellRenderer(ILocalDatePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private TableCellRenderer createDateTableCellRenderer(ILocalDateTimePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private IFormatter createDateFormatter(
            ILocalDatePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createDateFormatter(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDatePropertyDescriptor marker) {
        return new LocalDateFormatAdapter(format);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDateTimePropertyDescriptor marker) {
        return new LocalDateTimeFormatAdapter(format);
    }

    private LocalDate getDateTemplateValue(ILocalDatePropertyDescriptor marker) {
        return LocalDate.fromDateFields(TEMPLATE_DATE);
    }

    private LocalDateTime getDateTemplateValue(ILocalDateTimePropertyDescriptor marker) {
        return  LocalDateTime.fromDateFields(TEMPLATE_DATE);
    }
}

Wings

package org.popsuite.framework.view.wings;

[...]
/**
 * Manages the joda's dates
 */
public class PopsuiteWingsViewFactory
extends DefaultWingsViewFactory{

    private static final Date                  TEMPLATE_DATE               = new Date(27166271000L);
   
    @Override
    protected IView<SComponent> createPropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IView<SComponent> view = null;
        IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) { 
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        if (view = = null) {
            return super.createPropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        decorateWithDescription(propertyDescriptor, locale, view); // see AbstractViewFactory.createPropertyView
        return view;
    }

    @Override
    protected STableCellRenderer createTableCellRenderer(
            IPropertyDescriptor propertyDescriptor,
            Locale locale) {
        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            return createDateTableCellRenderer(
                    (ILocalDatePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            return createDateTableCellRenderer(
                    (ILocalDateTimePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        return super.createTableCellRenderer(propertyDescriptor, locale);
    }

    protected IView<SComponent> createLocalDatePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDatePropertyDescriptor propertyDescriptor = (ILocalDatePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        XCalendar viewComponent = createDateField();
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.setFormatter(new SDateFormatter(format));
        XCalendarConnector connector = new XJodaTimeConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                64);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    protected IView<SComponent> createLocalDateTimePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDateTimePropertyDescriptor propertyDescriptor = (ILocalDateTimePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        XCalendar viewComponent = createDateField();
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.setFormatter(new SDateFormatter(format));
        XCalendarConnector connector = new XJodaTimeConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                64);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    private STableCellRenderer createDateTableCellRenderer(ILocalDatePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private STableCellRenderer createDateTableCellRenderer(ILocalDateTimePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private IFormatter createDateFormatter(
            ILocalDatePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createDateFormatter(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }
   
    private IFormatter createFormatter(DateFormat format, ILocalDatePropertyDescriptor marker) {
        return new LocalDateFormatAdapter(format);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDateTimePropertyDescriptor marker) {
        return new LocalDateTimeFormatAdapter(format);
    }

    private LocalDate getDateTemplateValue(ILocalDatePropertyDescriptor marker) {
        return LocalDate.fromDateFields(TEMPLATE_DATE);
    }

    private LocalDateTime getDateTemplateValue(ILocalDateTimePropertyDescriptor marker) {
        return  LocalDateTime.fromDateFields(TEMPLATE_DATE);
    }
   
}

Flex and Qooxdoo

package org.popsuite.framework.view.remote;
[...]
/**
 * Manages the joda's dates
 */
public class PopsuiteRemoteViewFactory
extends DefaultRemoteViewFactory {
   
    /**
     * {@inheritDoc}
     */
    @Override
    protected IView<RComponent> createPropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IView<RComponent> view = null;
        IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) {  // must come before ILocalDateTimePropertyDescriptor
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            view = createLocalDateTimePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        if (view = = null) {
            return super.createPropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        decorateWithDescription(propertyDescriptor, locale, view); // see AbstractViewFactory.createPropertyView
        if ((view != null) && (propertyDescriptor.getName() != null)) { // see DefaultRemoteViewFactory.createPropertyView
            view.getPeer().setLabel(
                    propertyDescriptor.getI18nName(getTranslationProvider(), locale));
        }
        return view;
    }

    protected IView<RComponent> createLocalDatePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IValueConnector connector;
        ILocalDatePropertyDescriptor propertyDescriptor = (ILocalDatePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        if (isDateServerParse()) {
            connector = getConnectorFactory().createFormattedValueConnector(
                    propertyDescriptor.getName(),
                    createDateFormatter(propertyDescriptor, locale));
        } else {
            connector = getConnectorFactory().createValueConnector(
                    propertyDescriptor.getName());
        }

        connector.setExceptionHandler(actionHandler);
        RDateField viewComponent = createRDateField(connector);
        viewComponent.setType(propertyDescriptor.getType().toString());
        IView<RComponent> view = constructView(viewComponent, propertyViewDescriptor, connector);
        return view;
    }

    protected IView<RComponent> createLocalDateTimePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IValueConnector connector;
        ILocalDateTimePropertyDescriptor propertyDescriptor = (ILocalDateTimePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        if (isDateServerParse()) {
            connector = getConnectorFactory().createFormattedValueConnector(
                    propertyDescriptor.getName(),
                    createDateFormatter(propertyDescriptor, locale));
        } else {
            connector = getConnectorFactory().createValueConnector(
                    propertyDescriptor.getName());
        }

        connector.setExceptionHandler(actionHandler);
        RDateField viewComponent = createRDateField(connector);
        viewComponent.setType(propertyDescriptor.getType().toString());
        IView<RComponent> view = constructView(viewComponent, propertyViewDescriptor, connector);
        return view;
    }

    private IFormatter createDateFormatter(
            ILocalDatePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createDateFormatter(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDatePropertyDescriptor marker) {
        return new LocalDateFormatAdapter(format);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDateTimePropertyDescriptor marker) {
        return new LocalDateTimeFormatAdapter(format);
    }

}

Frontend configuration file

In the project frontend file, in the bean "applicationFrontController", for the attribute "viewFactory", the standard value "viewFactory" must be replaced by a specific one, for example "popsuiteViewFactory".

Then the bean "popsuiteViewFactory" has to be defined in new specific frontend files, one for each of the view factories.

In frontend-swing.xml:

  <bean
    id="popsuiteViewFactory"
    class="org.popsuite.framework.view.swing.PopsuiteSwingViewFactory"
    parent="viewFactory"
    scope="prototype">
  </bean>

In frontend-wings.xml:

  <bean
    id="popsuiteViewFactory"
    class="org.popsuite.framework.view.wings.PopsuiteWingsViewFactory"
    parent="viewFactory"
    scope="prototype">
  </bean>

In frontend-remote.xml:

  <bean
    id="popsuiteViewFactory"
    class="org.popsuite.framework.view.remote.PopsuiteRemoteViewFactory"
    parent="viewFactory"
    scope="prototype">
    <property name="connectorFactory"  ref="popsuiteConnectorFactory" />
  </bean>

Spring location file

As new frontend configuration files have been created, the project file "beanRefFactoy.xml" must know where to find them. It will be done for each UI context bean, e.g. for Swing (in bold):

  <bean
    id="popsuite9-swing-context"
    class="org.springframework.context.support.ClassPathXmlApplicationContext"
    lazy-init="true">
    <constructor-arg>
      <list>
        [...]
        <value>org/jspresso/framework/application/frontend/commons-frontend.xml</value>
        <value>org/jspresso/framework/application/frontend/swing/commons-swing.xml</value>
        [...]
        <value>org/popsuite/hr/frontend/frontend.xml</value>
        <value>org/popsuite/framework/application/frontend/frontend-swing.xml</value>
        [...]
      </list>
    </constructor-arg>
  </bean>

FormatAdapter

Each new model type needs a new format adapter. But each adaptor is used by all the view factories.

For Joda LocalDateTime:

package org.popsuite.framework.util.format;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.util.format.FormatAdapter;

/**
 * An adapter for the <code>Format</code> joda objects.
 *
 * Uses the jdk's Format with joda's objects
 *
 *
 */
public class LocalDateTimeFormatAdapter extends FormatAdapter {

    public LocalDateTimeFormatAdapter(DateFormat format) {
        super(format);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String format(Object value) {
        if (isUnknown(value)) return null;

        Date date;
        if (value instanceof LocalDateTime) {
            date = ((LocalDateTime) value).toDateTime().toDate();       
        } else if (value instanceof LocalDate) {
            date = ((LocalDate) value).toDateTimeAtStartOfDay().toDate();
        } else if (value instanceof Date) {
            date = (Date) value; // should never be used but...
        } else {
            return null;
        }
        String result = getFormat().format(date);
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object parse(String dateSource) throws ParseException {
        if (null = = dateSource || dateSource.isEmpty()) return null;

        Date date = ((DateFormat) getFormat()).parse(dateSource);
        return LocalDateTime.fromDateFields(date);
    }

}

And for Joda LocalDate:

package org.popsuite.framework.util.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;

import org.joda.time.LocalDate;

/**
 * An adapter for the <code>Format</code> joda objects.
 *
 * Uses the jdk's Format with joda's objects
 *
 */
public class LocalDateFormatAdapter extends LocalDateTimeFormatAdapter {

    public LocalDateFormatAdapter(DateFormat format) {
        super(format);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object parse(String dateSource) throws ParseException {
        if (null = = dateSource || dateSource.isEmpty()) return null;
       
        Date date = ((DateFormat) getFormat()).parse(dateSource);
        return LocalDate.fromDateFields(date);
    }

}

The Model template

Not the funiest part of the job, as at the moment there is no way to hook such an extension without changing the template "HibernateXdocletEntity.ftl" itself (°).

So:

- create a copy of the original in your project

- in {project_home}/pom.xml, add:

  <properties>
    <generator.templateResourcePath>{template_package_path}</generator.templateResourcePath>
  </properties>

- add the specific code to the template (see below).

In the template, there is only one place to change. The test on the new date types must be inserted before the test on IDatePropertyDescriptor. The new code is:

    <#if !componentDescriptor.computed && !propertyDescriptor.delegateClassName?exists>
      * @hibernate.property <#-- Order LocalDate > LocalDateTime > Date is mandatory -->
      <#if instanceof(propertyDescriptor, "org.popsuite.framework.model.descriptor.ILocalDatePropertyDescriptor")>
      *           type = "org.joda.time.contrib.hibernate.PersistentLocalDate"
      <#elseif instanceof(propertyDescriptor, "org.popsuite.framework.model.descriptor.ILocalDateTimePropertyDescriptor")>
      *           type = "org.joda.time.contrib.hibernate.PersistentLocalDateTime"
      <#elseif instanceof(propertyDescriptor, "org.jspresso.framework.model.descriptor.IDatePropertyDescriptor")>
      [...]

That's it.

Indeed, when using a snapshot version of jspresso, you need to check from time to time if there is some change in the original file and to patch them in the project copy.

Note (°) : the FreeMarker template can manage insertion of files. With such a mean, it should be possible to create hooks.

Hibernate configuration

It's the simplest part of the work. In your project:

- check the version of joda library used by default by jspresso, i.e. joda time 1.6+

- add the joda-time-hibernate library, at the moment version 1.1

- change the pom files in the project, i.e.:

  * in {project_home}/pom.xml, add:

      <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time-hibernate</artifactId>
        <version>1.1</version>
      </dependency>

  * in {project_home}/core/pom.xml, add (in bold):

      <dependencies>
      [...]
        <dependency>
          <groupId>joda-time</groupId>
          <artifactId>joda-time-hibernate</artifactId>
          <exclusions>
            <exclusion>
              <groupId>joda-time</groupId>
              <artifactId>joda-time</artifactId>
            </exclusion>
            <exclusion>
              <groupId>hibernate</groupId>
              <artifactId>hibernate</artifactId>
            </exclusion>
            <exclusion>
              <groupId>commons-logging</groupId>
              <artifactId>commons-logging-api</artifactId>
            </exclusion>
            <exclusion>
              <groupId>commons-collections</groupId>
              <artifactId>commons-collections</artifactId>
            </exclusion>
<exclusion>
<groupId>ehcache</groupId>
<artifactId>ehcache</artifactId>
</exclusion>
            <exclusion>
              <groupId>dom4j</groupId>
              <artifactId>dom4j</artifactId>
            </exclusion>
            <exclusion>
              <groupId>antlr</groupId>
              <artifactId>antlr</artifactId>
            </exclusion>
          </exclusions>
        </dependency>
      [...]
      </dependencies>
      [...]
      <profiles>
        <profile>
          <id>ulc</id>
          [...]
          <build>
            <plugins>
            [...]
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                  <execution>
                  [...]
                  <configuration>
                    <outputDirectory>${project.build.directory}/${project.artifactId}/lib</outputDirectory>
                    <includeArtifactIds>ulc-base-trusted,(...],joda-time,joda-time-hibernate,substance</includeArtifactIds>
                    <stripVersion>true</stripVersion>
                  </configuration>
                  [...]