Property Editors

This example demonstrates adding your own property editors, allowing Grid, BeanDisplay, and BeanEditor to display/edit unusual property types.

A property editor is made up of two parts:

  1. A data type analyzer, able to recognise when a property is of a type that the editor can handle.
  2. A display block and/or edit block, to display and/or edit the property.
As Grid or BeanDisplay tries to render each readable property of an object, it analyzes the property's data type, then renders the corresponding display block if one exists.
Tapestry comes with display blocks for enum, date, calendar, password, and more. See here.

As Grid or BeanEditor tries to render each read-write property of an object, it analyzes the property's data type, then renders the corresponding edit block if one exists.
Tapestry comes with edit blocks for text, number, enum, date, and more. See here.

In JumpStart we have added to the list of supported types by contributing property editors for certain Joda Time properties:

  1. We contributed DataTypeAnalyzers to detect 5 types: DateTime, DateMidnight, LocalDateTime, LocalDate, and LocalTime.
  2. We contributed BeanBlockSource display blocks for all 5 types and edit blocks for 2 of them: DateMidnight and LocalDate.
You can see the contributions in AppModule, below.

Here is what BeanDisplay can do with a DatesExample object. It makes use of our 5 contributed display blocks.
Id
1
Version
9
ADate Time
July 31, 2001 10:35:17 AM UTC
ADate Midnight
March 20, 2024
ALocal Date Time
July 31, 2001 10:35:17 AM
ALocal Date
July 24, 2001
ALocal Time As Time
10:35:17 AM
Here is what BeanEditForm can do with the same DatesExample object. It makes use of our 2 contributed edit blocks.
References: BeanEditForm Guide, DataTypeAnalyzer, BeanBlockSource, DisplayBlockContribution, EditBlockContribution, PropertyOutputContext, PropertyEditContext, DateField, TextField, @InjectComponent, Joda Time.

Home

The source for IPersonFinderServiceLocal and @EJB is shown in the Session Beans and @EJB examples.


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like &nbsp; 
     We use a "strict" DTD to make IE follow the alignment rules. -->
     
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
    <h3>Property Editors</h3>
    
    This example demonstrates adding your own property editors, allowing Grid, BeanDisplay, and BeanEditor to display/edit unusual property types.<br/><br/>
    
    <p>A property editor is made up of two parts:</p>
    <ol>
        <li>A <strong>data type analyzer</strong>, able to recognise when a property is of a type that the editor can handle.</li>
        <li>A <strong>display block</strong> and/or <strong>edit block</strong>, to display and/or edit the property.</li>
    </ol>
    
    As Grid or BeanDisplay tries to render each <strong>readable property</strong> of an object, it analyzes the property's data type, then renders the 
    corresponding <strong>display block</strong> if one exists. <br/>
    Tapestry comes with display blocks for enum, date, calendar, password, and more. 
    See <a href="http://tapestry.apache.org/beaneditform-guide.html#BeanEditFormGuide-SupportedTypes">here</a>.<br/><br/>
    
    As Grid or BeanEditor tries to render each <strong>read-write property</strong> of an object, it analyzes the property's data type, then renders the 
    corresponding <strong>edit block</strong> if one exists. <br/>
    Tapestry comes with edit blocks for text, number, enum, date, and more. 
    See <a href="http://tapestry.apache.org/beaneditform-guide.html#BeanEditFormGuide-SupportedTypes">here</a>.<br/><br/>

    <p>In JumpStart we have added to the list of supported types by contributing property editors for certain Joda Time properties:</p>
    <ol>
        <li>We contributed <strong>DataTypeAnalyzers</strong> to detect 5 types: DateTime, DateMidnight, LocalDateTime, LocalDate, and LocalTime.</li>
        <li>We contributed BeanBlockSource <strong>display blocks</strong> for all 5 types and <strong>edit blocks</strong> for 2 of them: DateMidnight and LocalDate.</li>
    </ol>
    You can see the contributions in AppModule, below.<br/><br/>
    
    Here is what BeanDisplay can do with a DatesExample object. It makes use of our 5 contributed display blocks. 
        
    <div class="eg">
        <t:beandisplay object="datesExample" include="id,version,adatetime,adatemidnight,alocaldatetime,alocaldate,alocaltimeastime"/>
    </div>
            
    Here is what BeanEditForm can do with the same DatesExample object. It makes use of our 2 contributed edit blocks.
    
    <div class="eg">    
        <t:beaneditform t:id="updateDates" object="datesExample" include="adatemidnight,alocaldate" submitLabel="Save"/>
    </div>
        
    References: 
    <a href="http://tapestry.apache.org/beaneditform-guide.html">BeanEditForm Guide</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/DataTypeAnalyzer.html">DataTypeAnalyzer</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/BeanBlockSource.html">BeanBlockSource</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/DisplayBlockContribution.html">DisplayBlockContribution</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/EditBlockContribution.html">EditBlockContribution</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/PropertyOutputContext.html">PropertyOutputContext</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/PropertyEditContext.html">PropertyEditContext</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/DateField.html">DateField</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/TextField.html">TextField</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/annotations/InjectComponent.html">@InjectComponent</a>, 
    <a href="http://joda-time.sourceforge.net/">Joda Time</a>.<br/><br/>
    
    <t:pagelink page="Index">Home</t:pagelink><br/><br/>
    
    The source for IPersonFinderServiceLocal and @EJB is shown in the Session Beans and @EJB examples.<br/><br/>

    <t:tabgroup>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/lang/PropertyEditors.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/lang/PropertyEditors.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.css"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/infra/AppPropertyDisplayBlocks.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/infra/AppPropertyDisplayBlocks.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/infra/AppPropertyEditBlocks.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/infra/AppPropertyEditBlocks.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/services/AppModule.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/datestuff/DateStuffService.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/datestuff/DatesExample.java"/>
    </t:tabgroup>
</body>
</html>


package jumpstart.web.pages.examples.lang;

import javax.ejb.EJB;

import jumpstart.business.domain.datestuff.DatesExample;
import jumpstart.business.domain.datestuff.iface.IDateStuffServiceLocal;
import jumpstart.util.ExceptionUtil;

import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.BeanEditForm;

@Import(stylesheet = "css/examples/plain.css")
public class PropertyEditors {

    // Screen fields

    @Property
    private DatesExample datesExample;

    // Generally useful bits and pieces

    @EJB
    private IDateStuffServiceLocal dateStuffService;

    @InjectComponent("updateDates")
    private BeanEditForm form;

    // The code

    void setupRender() throws Exception {
        datesExample = findDatesExample(1L);
    }

    void onPrepareForSubmit() throws Exception {
        datesExample = findDatesExample(1L);
    }

    void onValidateFromUpdateDates() {
        try {
            // if (datesExample.getADateMidnight() == null || datesExample.getALocalDate() == null) {
            // form.recordError("Both dates are required.");
            // return;
            // }
            dateStuffService.changeDatesExample(datesExample);
        }
        catch (Exception e) {
            // Display the cause. In a real system we would try harder to get a user-friendly message.
            form.recordError(ExceptionUtil.getRootCauseMessage(e));
        }
    }

    private DatesExample findDatesExample(Long id) throws Exception {
        // Ask business service to find DatesExample
        DatesExample datesExample = dateStuffService.findDatesExample(id);

        if (datesExample == null) {
            throw new IllegalStateException("Database data has not been set up!");
        }

        return datesExample;
    }

}


.eg {
                margin: 20px 0;
                padding: 14px;
                border: 1px solid #ddd;
                border-radius: 6px;
                -webkit-border-radius: 6px;
                -mox-border-radius: 6px;
}


<t:container xml:space="default" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">

    <t:block id="dateTime">
        <t:jodatimeoutput value="context.propertyValue" style="LL"/>
    </t:block>

    <t:block id="dateMidnight">
        <t:jodatimeoutput value="context.propertyValue" style="L-"/>
    </t:block>

    <t:block id="localDateTime">
        <t:jodatimeoutput value="context.propertyValue" style="LL"/>
    </t:block>

    <t:block id="localDate">
        <t:jodatimeoutput value="context.propertyValue" style="L-"/>
    </t:block>

    <t:block id="localTime">
        <t:jodatimeoutput value="context.propertyValue" style="-L"/>
    </t:block>

</t:container>


// Based on http://tapestry.apache.org/tapestry5/guide/beaneditform.html

package jumpstart.web.pages.infra;

import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.services.PropertyOutputContext;

public class AppPropertyDisplayBlocks {

    @Property
    @Environmental
    private PropertyOutputContext context;

}


<t:container xml:space="default" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">

    <t:block id="dateMidnight">
        <t:label for="dateMidnight"/>
        <input t:id="dateMidnight" t:type="DateField" value="context.propertyValue" label="prop:context.label" 
            format="prop:dateInputFormat" translate="prop:dateMidnightTranslator" validate="prop:dateMidnightValidator" 
            clientId="prop:context.propertyId" annotationProvider="context"/>
    </t:block>
    
    <t:block id="localDate">
        <t:label for="localDate"/>
        <input t:id="localDate" t:type="DateField" value="context.propertyValue" label="prop:context.label" 
            format="prop:dateInputFormat" translate="prop:localDateTranslator" validate="prop:localDateValidator" 
            clientId="prop:context.propertyId" annotationProvider="context"/>
    </t:block>

</t:container>


// Based on http://tapestry.apache.org/tapestry5/guide/beaneditform.html

package jumpstart.web.pages.infra;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import org.apache.tapestry5.FieldTranslator;
import org.apache.tapestry5.FieldValidator;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.DateField;
import org.apache.tapestry5.services.PropertyEditContext;

public class AppPropertyEditBlocks {

    @Property
    @Environmental
    private PropertyEditContext context;

    @InjectComponent
    private DateField dateMidnight;

    @InjectComponent
    private DateField localDate;

    public DateFormat getDateInputFormat() {
        return new SimpleDateFormat("dd MMMM yyyy");
    }

    public FieldTranslator<?> getDateMidnightTranslator() {
        return context.getTranslator(dateMidnight);
    }

    public FieldValidator<?> getDateMidnightValidator() {
        return context.getValidator(dateMidnight);
    }

    public FieldTranslator<?> getLocalDateTranslator() {
        return context.getTranslator(localDate);
    }

    public FieldValidator<?> getLocalDateValidator() {
        return context.getValidator(localDate);
    }

}


package jumpstart.web.services;

import java.util.Map;

import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.Translator;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptor;
import org.apache.tapestry5.internal.beanvalidator.BaseCCD;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Primary;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
import org.apache.tapestry5.ioc.services.Coercion;
import org.apache.tapestry5.ioc.services.CoercionTuple;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.apache.tapestry5.services.BeanBlockContribution;
import org.apache.tapestry5.services.ComponentRequestFilter;
import org.apache.tapestry5.services.DisplayBlockContribution;
import org.apache.tapestry5.services.EditBlockContribution;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.DataConstants;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.services.security.WhitelistAnalyzer;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.upload.services.UploadSymbols;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.slf4j.Logger;

import jumpstart.business.validation.constraints.Letters;
import jumpstart.util.JodaTimeUtil;
import jumpstart.web.translators.MoneyTranslator;
import jumpstart.web.translators.YesNoTranslator;

/**
 * This module is automatically included as part of the Tapestry IoC Registry, it's a good place to configure and extend
 * Tapestry, or to place your own service definitions. See http://tapestry.apache.org/5.3.4/tapestry-ioc/module.html
 */
public class AppModule {
    private static final String UPLOADS_PATH = "jumpstart.upload-path";

    @Inject
    @Symbol(SymbolConstants.PRODUCTION_MODE)
    @Property(write = false)
    private static boolean productionMode;

    // Add 2 services to those provided by Tapestry.
    // - CountryNames, and SelectIdModelFactory are used by pages which ask Tapestry to @Inject them.

    public static void bind(ServiceBinder binder) {
        binder.bind(CountryNames.class);
        binder.bind(SelectIdModelFactory.class, SelectIdModelFactoryImpl.class);
    }

    // Tell Tapestry about our custom translators and their message file.
    // We do this by contributing configuration to Tapestry's TranslatorAlternatesSource service, FieldValidatorSource
    // service, and ComponentMessagesSource service.

    @SuppressWarnings("rawtypes")
    public static void contributeTranslatorAlternatesSource(MappedConfiguration<String, Translator> configuration,
            ThreadLocale threadLocale) {
        configuration.add("yesno", new YesNoTranslator("yesno"));
        configuration.add("money2", new MoneyTranslator("money2", 2, threadLocale));
    }

    public void contributeComponentMessagesSource(OrderedConfiguration<String> configuration) {
        configuration.add("myTranslationMessages", "jumpstart/web/translators/TranslationMessages");
    }

    // Tell Tapestry about the client-side (javascript) validators that corresponds to each server-side Bean Validator.

    public static void contributeClientConstraintDescriptorSource(final JavaScriptSupport javaScriptSupport,
            final Configuration<ClientConstraintDescriptor> configuration) {

        configuration.add(new BaseCCD(Letters.class) {

            public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes) {
                javaScriptSupport.require("beanvalidation/letters");
                writer.attributes(DataConstants.VALIDATION_ATTRIBUTE, true, "data-validate-letters", true,
                        "data-letters-message", message);
            }

        });

    }

    // Tell Tapestry about our custom ValueEncoders.
    // We do this by contributing configuration to Tapestry's ValueEncoderSource service.

    // @SuppressWarnings("rawtypes")
    // public static void contributeValueEncoderSource(MappedConfiguration<Class, Object> configuration) {
    // configuration.addInstance(Person.class, PersonEncoder.class);
    // }

    // Tell Tapestry which locales we support, and tell Tapestry to use its jQuery implementation for its JavaScript.
    // We do this by contributing configuration to Tapestry's ApplicationDefaults service.

    public static void contributeApplicationDefaults(MappedConfiguration<String, String> configuration) {
        configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en_US,en_GB,fr");
        configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "jquery");
    }

    // Tell Tapestry how to detect and protect pages that require security.
    // We do this by contributing a custom ComponentRequestFilter to Tapestry's ComponentRequestHandler service.
    // - ComponentRequestHandler is shown in
    // http://tapestry.apache.org/request-processing.html#RequestProcessing-Overview
    // - Based on http://tapestryjava.blogspot.com/2009/12/securing-tapestry-pages-with.html

    public void contributeComponentRequestHandler(OrderedConfiguration<ComponentRequestFilter> configuration) {
        configuration.addInstance("PageProtectionFilter", PageProtectionFilter.class);
    }

    // Tell Tapestry how to handle WildFly 11's classpath URLs - WildFly uses a "virtual file system".
    // We do this by overriding Tapestry's ClasspathURLConverter service.
    // See "Running Tapestry on JBoss" (sic) in http://wiki.apache.org/tapestry/Tapestry5HowTos .

    @SuppressWarnings("rawtypes")
    public static void contributeServiceOverride(MappedConfiguration<Class, Object> configuration) {
        // configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterJBoss7());
        configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterWildFly11());
    }

    // Tell Tapestry how to handle @EJB in page and component classes.
    // We do this by contributing configuration to Tapestry's ComponentClassTransformWorker service.
    // - Based on http://wiki.apache.org/tapestry/JEE-Annotation.

    @Primary
    public static void contributeComponentClassTransformWorker(
            OrderedConfiguration<ComponentClassTransformWorker2> configuration) {
        configuration.addInstance("EJB", EJBAnnotationWorker.class, "before:Property");
    }

    // Tell Tapestry how to handle pages annotated with @WhitelistAccessOnly, eg. Tapestry's ServiceStatus and
    // PageCatalog.
    // The default WhitelistAnalyzer allows localhost only and only in non-production mode.
    // Our aim is to make the servicestatus page available to ALL clients when not in production mode.
    // We do this by contributing our own WhitelistAnalyzer to Tapestry's ClientWhitelist service.

    public static void contributeClientWhitelist(OrderedConfiguration<WhitelistAnalyzer> configuration) {
        if (!productionMode) {
            configuration.add("NonProductionWhitelistAnalyzer", new WhitelistAnalyzer() {
                @Override
                public boolean isRequestOnWhitelist(Request request) {
                    if (request.getPath().startsWith("/core/servicestatus")) {
                        return true;
                    }
                    else {
                        // This is copied from org.apache.tapestry5.internal.services.security.LocalhostOnly
                        String remoteHost = request.getRemoteHost();
                        return remoteHost.equals("localhost") || remoteHost.equals("127.0.0.1")
                                || remoteHost.equals("0:0:0:0:0:0:0:1%0") || remoteHost.equals("0:0:0:0:0:0:0:1");
                    }
                }
            }, "before:*");
        }
    }

    // Tell Tapestry how to build our Filer service (used in the FileUpload example).
    // Annotate it with EagerLoad to force resolution of symbols at startup rather than when it is first used.

    @EagerLoad
    public static IFiler buildFiler(Logger logger, @Inject @Symbol(UPLOADS_PATH) final String uploadsPath,
            @Inject @Symbol(UploadSymbols.FILESIZE_MAX) final long fileSizeMax) {
        return new Filer(logger, UPLOADS_PATH, uploadsPath, UploadSymbols.FILESIZE_MAX, fileSizeMax);
    }

    // Tell Tapestry how to coerce Joda Time types to and from Java Date types for the TypeCoercers example.
    // We do this by contributing configuration to Tapestry's TypeCoercer service.
    // - Based on http://tapestry.apache.org/typecoercer-service.html

    @SuppressWarnings("rawtypes")
    public static void contributeTypeCoercer(Configuration<CoercionTuple> configuration) {

        // From java.util.Date to DateMidnight

        Coercion<java.util.Date, DateMidnight> toDateMidnight = new Coercion<java.util.Date, DateMidnight>() {
            public DateMidnight coerce(java.util.Date input) {
                // TODO - confirm this conversion always works, esp. across timezones
                return JodaTimeUtil.toDateMidnight(input);
            }
        };

        configuration.add(new CoercionTuple<>(java.util.Date.class, DateMidnight.class, toDateMidnight));

        // From DateMidnight to java.util.Date

        Coercion<DateMidnight, java.util.Date> fromDateMidnight = new Coercion<DateMidnight, java.util.Date>() {
            public java.util.Date coerce(DateMidnight input) {
                // TODO - confirm this conversion always works, esp. across timezones
                return JodaTimeUtil.toJavaDate(input);
            }
        };

        configuration.add(new CoercionTuple<>(DateMidnight.class, java.util.Date.class, fromDateMidnight));

        // From java.util.Date to LocalDate

        Coercion<java.util.Date, LocalDate> toLocalDate = new Coercion<java.util.Date, LocalDate>() {
            public LocalDate coerce(java.util.Date input) {
                // TODO - confirm this conversion always works, esp. across timezones
                return JodaTimeUtil.toLocalDate(input);
            }
        };

        configuration.add(new CoercionTuple<>(java.util.Date.class, LocalDate.class, toLocalDate));

        // From LocalDate to java.util.Date

        Coercion<LocalDate, java.util.Date> fromLocalDate = new Coercion<LocalDate, java.util.Date>() {
            public java.util.Date coerce(LocalDate input) {
                // TODO - confirm this conversion always works, esp. across timezones
                return JodaTimeUtil.toJavaDate(input);
            }
        };

        configuration.add(new CoercionTuple<>(LocalDate.class, java.util.Date.class, fromLocalDate));
    }

    // Tell Tapestry how its BeanDisplay and BeanEditor can handle the JodaTime types.
    // We do this by contributing configuration to Tapestry's DefaultDataTypeAnalyzer and BeanBlockSource services.
    // - Based on http://tapestry.apache.org/beaneditform-guide.html .

    public static void contributeDefaultDataTypeAnalyzer(
            @SuppressWarnings("rawtypes") MappedConfiguration<Class, String> configuration) {
        configuration.add(DateTime.class, "dateTime");
        configuration.add(DateMidnight.class, "dateMidnight");
        configuration.add(LocalDateTime.class, "localDateTime");
        configuration.add(LocalDate.class, "localDate");
        configuration.add(LocalTime.class, "localTime");
    }

    public static void contributeBeanBlockSource(Configuration<BeanBlockContribution> configuration) {

        configuration.add(new DisplayBlockContribution("dateTime", "infra/AppPropertyDisplayBlocks", "dateTime"));
        configuration
                .add(new DisplayBlockContribution("dateMidnight", "infra/AppPropertyDisplayBlocks", "dateMidnight"));
        configuration
                .add(new DisplayBlockContribution("localDateTime", "infra/AppPropertyDisplayBlocks", "localDateTime"));
        configuration.add(new DisplayBlockContribution("localDate", "infra/AppPropertyDisplayBlocks", "localDate"));
        configuration.add(new DisplayBlockContribution("localTime", "infra/AppPropertyDisplayBlocks", "localTime"));

        configuration.add(new EditBlockContribution("dateMidnight", "infra/AppPropertyEditBlocks", "dateMidnight"));
        configuration.add(new EditBlockContribution("localDate", "infra/AppPropertyEditBlocks", "localDate"));

    }

}


package jumpstart.business.domain.datestuff;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import jumpstart.business.domain.datestuff.iface.IDateStuffServiceLocal;
import jumpstart.business.domain.datestuff.iface.IDateStuffServiceRemote;

@Stateless
@Local(IDateStuffServiceLocal.class)
@Remote(IDateStuffServiceRemote.class)
public class DateStuffService implements IDateStuffServiceLocal, IDateStuffServiceRemote {

    @PersistenceContext(unitName = "jumpstart")
    private EntityManager em;

    public DatesExample findDatesExample(Long id) {
        return em.find(DatesExample.class, id);
    }

    public DatesExample changeDatesExample(DatesExample datesExample) {
        return em.merge(datesExample);
    }

}


package jumpstart.business.domain.datestuff;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;

import jumpstart.util.JodaTimeUtil;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;

/**
 * The DatesExample entity.
 */
@Entity
@SuppressWarnings("serial")
public class DatesExample implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private Long id;

    @Version
    @Column(nullable = false)
    private Integer version;

    // Traditional java-style Date fields

    @Temporal(TemporalType.TIMESTAMP)
    private java.util.Date aTimestamp;

    @Temporal(TemporalType.DATE)
    private java.util.Date aDate;

    @Temporal(TemporalType.TIME)
    private java.util.Date aTime;

    // These fields are exposed as Joda Time types but persisted as standard Java types (Date, Timestamp, String,
    // Integer).

    private java.sql.Timestamp aDateTime;

    private java.sql.Timestamp aDateTimeWithTZ;

    private String aDateTimeTZ;

    // This JSR-303 validation will be picked up by Form. For BeanEditForm, we also annotate the getter.
    @NotNull
    private java.sql.Date aDateMidnight;

    private java.sql.Date aDateMidnightWithTZ;

    private String aDateMidnightTZ;

    private java.sql.Timestamp aLocalDateTime;

    // This JSR-303 validation will be picked up by Form. For BeanEditForm, we also annotate the getter.
    @NotNull
    private java.sql.Date aLocalDate;

    private java.sql.Time aLocalTimeAsTime;

    private Integer aLocalTimeAsMillis;

    private String aLocalTimeAsString;

    public String toString() {
        final String DIVIDER = ", ";

        StringBuilder buf = new StringBuilder();
        buf.append(this.getClass().getSimpleName() + ": ");
        buf.append("[");
        buf.append("id=" + id + DIVIDER);
        buf.append("aTimestamp=" + aTimestamp + DIVIDER);
        buf.append("aDate=" + aDate + DIVIDER);
        buf.append("aTime=" + aTime + DIVIDER);
        buf.append("aDateTime=" + aDateTime + DIVIDER);
        buf.append("aDateTimeWithTZ=" + aDateTimeWithTZ + DIVIDER);
        buf.append("aDateTimeTZ=" + aDateTimeTZ + DIVIDER);
        buf.append("aDateMidnight=" + aDateMidnight + DIVIDER);
        buf.append("aLocalDateTime=" + aLocalDateTime + DIVIDER);
        buf.append("aLocalDate=" + aLocalDate + DIVIDER);
        buf.append("aLocalTimeAsTime=" + aLocalTimeAsTime + DIVIDER);
        buf.append("aLocalTimeAsMillis=" + aLocalTimeAsMillis + DIVIDER);
        buf.append("aLocalTimeAsString=" + aLocalTimeAsString + DIVIDER);
        buf.append("version=" + version);
        buf.append("]");
        return buf.toString();
    }

    // The need for an equals() method is discussed at http://www.hibernate.org/109.html

    @Override
    public boolean equals(Object obj) {
        return (obj == this) || (obj instanceof DatesExample) && id != null && id.equals(((DatesExample) obj).getId());
    }

    // The need for a hashCode() method is discussed at http://www.hibernate.org/109.html

    @Override
    public int hashCode() {
        return id == null ? super.hashCode() : id.hashCode();
    }

    public Long getId() {
        return id;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public Date getADate() {
        return aDate;
    }

    public void setADate(Date aDate) {
        this.aDate = aDate;
    }

    public Date getATime() {
        return aTime;
    }

    public void setATime(Date aTime) {
        this.aTime = aTime;
    }

    public Date getATimestamp() {
        return aTimestamp;
    }

    public void setATimestamp(Date aTimestamp) {
        this.aTimestamp = aTimestamp;
    }

    public DateTime getADateTime() {
        return JodaTimeUtil.toDateTime(aDateTime);
    }

    public void setADateTime(DateTime dt) {
        this.aDateTime = JodaTimeUtil.toSQLTimestamp(dt);
    }

    public DateTime getADateTimeWithTZ() {
        return JodaTimeUtil.toDateTime(aDateTimeWithTZ, aDateTimeTZ);
    }

    public void setADateTimeWithTZ(DateTime dt) {
        this.aDateTimeWithTZ = JodaTimeUtil.toSQLTimestamp(dt);
        this.aDateTimeTZ = JodaTimeUtil.toTimeZoneID(dt);
    }

    // This JSR-303 validation will be picked up by BeanEditForm. For Form, we also annotate the field.
    @NotNull
    public DateMidnight getADateMidnight() {
        return JodaTimeUtil.toDateMidnight(aDateMidnight);
    }

    public void setADateMidnight(DateMidnight dm) {
        this.aDateMidnight = JodaTimeUtil.toSQLDate(dm);
    }

    public DateMidnight getADateMidnightWithTZ() {
        return JodaTimeUtil.toDateMidnight(aDateMidnightWithTZ, aDateMidnightTZ);
    }

    public void setADateMidnightWithTZ(DateMidnight dm) {
        this.aDateMidnightWithTZ = JodaTimeUtil.toSQLDate(dm);
        this.aDateMidnightTZ = JodaTimeUtil.toTimeZoneID(dm);
    }

    public LocalDateTime getALocalDateTime() {
        return JodaTimeUtil.toLocalDateTime(aLocalDateTime);
    }

    public void setALocalDateTime(LocalDateTime ldt) {
        this.aLocalDateTime = JodaTimeUtil.toSQLTimestamp(ldt);
    }

    // This JSR-303 validation will be picked up by BeanEditForm. For Form, we also annotate the field.
    @NotNull
    public LocalDate getALocalDate() {
        return JodaTimeUtil.toLocalDate(aLocalDate);
    }

    public void setALocalDate(LocalDate ld) {
        this.aLocalDate = JodaTimeUtil.toSQLDate(ld);
    }

    public LocalTime getALocalTimeAsTime() {
        return JodaTimeUtil.toLocalTime(aLocalTimeAsTime);
    }

    public void setALocalTimeAsTime(LocalTime lt) {
        this.aLocalTimeAsTime = JodaTimeUtil.toSQLTime(lt);
    }

    public LocalTime getALocalTimeAsMillis() {
        return JodaTimeUtil.toLocalTime(aLocalTimeAsMillis);
    }

    public void setALocalTimeAsMillis(LocalTime lt) {
        this.aLocalTimeAsMillis = JodaTimeUtil.toIntegerMillis(lt);
    }

    public LocalTime getALocalTimeAsString() {
        return JodaTimeUtil.toLocalTime(aLocalTimeAsString);
    }

    public void setALocalTimeAsString(LocalTime lt) {
        this.aLocalTimeAsString = JodaTimeUtil.toString(lt);
    }

}