Type Coercers

This example demonstrates adding your own type coercers.

Type coercion allows you to pass a value of the wrong type to a component's parameter. Tapestry will see it and ask its TypeCoercer service to "coerce" it to the right type.

This enables passing a Long to an Integer parameter, a String to a Boolean parameter, a BigDecimal to a Double parameter, and so on. The full list is here.

Tapestry's DateField works with java.util.Date. Let's say you also wanted it to work with Joda Time's DateMidnight and LocalDate.
To achieve this, we have contributed 4 type coercers. You can see the contribution in AppModule, below.
To help you debug coercions, you can ask the TypeCoercer service to explain how it will coerce one type to another.
Explain Long to Integer: Long --> Integer
Explain String to Integer: String --> Long, Long --> Integer
Explain Date to DateMidnight: java.util.Date --> org.joda.time.DateMidnight
References: Form, Type Coercion, TypeCoercer Service, TypeCoercer, DateField, 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>Type Coercers</h3>
    
    This example demonstrates adding your own type coercers.<br/><br/>

    Type coercion allows you to pass a value of the wrong type to a component's parameter. Tapestry will see it and ask its TypeCoercer 
    service to "coerce" it to the right type.<br/><br/>
    
    This enables passing a Long to an Integer parameter, a String to a Boolean parameter, a BigDecimal to a Double parameter, and so on. 
    <a href="http://tapestry.apache.org/typecoercer-service.html">The full list is here.</a><br/><br/>

    Tapestry's DateField works with java.util.Date. Let's say you also wanted it to work with Joda Time's DateMidnight and LocalDate.<br/>
    To achieve this, we have contributed 4 type coercers. You can see the contribution in AppModule, below.<br/>

    <div class="eg">
        <t:form t:id="dates" class="form-horizontal" validate="datesExample">
            <div class="form-group">
                <t:label for="aDateMidnight" class="col-sm-4"/>
                <div class="col-sm-4">
                    <t:datefield t:id="aDateMidnight" value="datesExample.aDateMidnight" format="prop:dateFieldFormat"/>
                </div>
            </div>
            <div class="form-group">
                <t:label for="aLocalDate" class="col-sm-4"/>
                <div class="col-sm-4">
                    <t:datefield t:id="aLocalDate" value="datesExample.aLocalDate" format="prop:dateFieldFormat"/>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-4 col-sm-offset-4">
                    <t:submit value="Save"/>
                    <t:eventlink event="refresh" class="btn btn-default">Refresh</t:eventlink>
                </div>
            </div>
        </t:form>
    </div>
    
    To help you debug coercions, you can ask the TypeCoercer service to 
    <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/services/TypeCoercer.html#explain(java.lang.Class, java.lang.Class)">explain</a> 
    how it will coerce one type to another.
    
    <div class="eg">
        Explain Long to Integer: ${explainLongToInteger}<br/>
        Explain String to Integer: ${explainStringToInteger}<br/>
        Explain Date to DateMidnight: ${explainDateToDateMidnight}
    </div> 
            
    References: 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/Form.html">Form</a>, 
    <a href="http://tapestry.apache.org/type-coercion.html">Type Coercion</a>, 
    <a href="http://tapestry.apache.org/typecoercer-service.html">TypeCoercer Service</a>, 
    <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/services/TypeCoercer.html">TypeCoercer</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/DateField.html">DateField</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/TypeCoercers.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/lang/TypeCoercers.properties"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/lang/TypeCoercers.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.css"/>
        <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:sourcecodetab src="/web/src/main/java/jumpstart/web/services/AppModule.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/util/JodaTimeUtil.java"/>
    </t:tabgroup>
</body>
</html>


aDateMidnight-label=A field of type org.joda.time.DateMidnight
aLocalDate-label=A field of type org.joda.time.LocalDate


package jumpstart.web.pages.examples.lang;

import java.util.Date;

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.PersistenceConstants;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.joda.time.DateMidnight;

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

    // Screen fields

    @Property
    private DatesExample datesExample;

    // Work fields

    // This carries version through the redirect that follows a server-side validation failure.
    @Persist(PersistenceConstants.FLASH)
    private Integer versionFlash;

    // Generally useful bits and pieces

    @EJB
    private IDateStuffServiceLocal dateStuffService;

    @InjectComponent("dates")
    private Form form;

    @Inject
    private TypeCoercer typeCoercer;

    // The code

    // Form bubbles up the PREPARE_FOR_RENDER event during form render.

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

        // If the form has errors then we're redisplaying after a redirect.
        // Form will restore your input values but it's up to us to restore Hidden values.

        if (form.getHasErrors()) {
            datesExample.setVersion(versionFlash);
        }
    }

    // Form bubbles up the PREPARE_FOR_SUBMIT event during form submission.

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

    void onValidateFromDates() {
        try {
            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));
        }
    }

    void onFailure() {
        versionFlash = datesExample.getVersion();
    }

    void onRefresh() {
        // By doing nothing the page will be displayed afresh.
    }

    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;
    }

    public String getDateFieldFormat() {
        return "dd MMMM yyyy";
    }

    public String getExplainLongToInteger() {
        try {
            return typeCoercer.explain(Long.class, Integer.class);
        }
        catch (Exception e) {
            return e.getMessage();
        }
    }

    public String getExplainStringToInteger() {
        try {
            return typeCoercer.explain(String.class, Integer.class);
        }
        catch (Exception e) {
            return e.getMessage();
        }
    }

    public String getExplainDateToDateMidnight() {
        try {
            return typeCoercer.explain(Date.class, DateMidnight.class);
        }
        catch (Exception e) {
            return e.getMessage();
        }
    }
}


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


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);
    }

}


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 JBoss 7's classpath URLs - JBoss uses a "virtual file system".
    // We do this by overriding Tapestry's ClasspathURLConverter service.
    // See "Running Tapestry on JBoss" in http://wiki.apache.org/tapestry/Tapestry5HowTos .

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

    // 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.util;

import java.util.TimeZone;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class JodaTimeUtil {
    public static final DateTimeFormatter localDateFormatter = DateTimeFormat.forPattern("YYYYMMDD");

    // ///////////////////////////////////////////
    // From JodaTime types to standard Java types
    // ///////////////////////////////////////////

    public static java.sql.Date toSQLDate(DateMidnight dm) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Date d = (dm == null ? null : new java.sql.Date(dm.getMillis()));
        return d;
    }

    public static java.util.Date toJavaDate(DateMidnight dm) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date d = (dm == null ? null : new java.util.Date(dm.getMillis()));
        return d;
    }

    public static String toTimeZoneID(DateMidnight dm) {
        String s = (dm == null ? null : dm.getZone().getID());
        return s;
    }

    public static java.sql.Timestamp toSQLTimestamp(DateTime dt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Timestamp ts = (dt == null ? null : new java.sql.Timestamp(dt.getMillis()));
        return ts;
    }

    public static java.util.Date toJavaDate(DateTime dt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date ts = (dt == null ? null : new java.util.Date(dt.getMillis()));
        return ts;
    }

    public static String toTimeZoneID(DateTime dt) {
        String s = (dt == null ? null : dt.getZone().getID());
        return s;
    }

    public static java.sql.Date toSQLDate(LocalDate ld) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Date d = (ld == null ? null : new java.sql.Date(ld.toDateTimeAtStartOfDay().getMillis()));
        return d;
    }

    public static java.util.Date toJavaDate(LocalDate ld) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date d = (ld == null ? null : new java.util.Date(ld.toDateTimeAtStartOfDay().getMillis()));
        return d;
    }

    public static String toString(LocalDate ld) {
        // TODO - confirm this conversion always works, esp. across timezones
        String s = (ld == null ? null : localDateFormatter.withZone(DateTimeZone.UTC).print(ld));
        return s;
    }

    public static java.sql.Timestamp toSQLTimestamp(LocalDateTime ldt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Timestamp ts = (ldt == null ? null : new java.sql.Timestamp(ldt.toDateTime().getMillis()));
        return ts;
    }

    public static java.util.Date toJavaDate(LocalDateTime ldt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.util.Date d = (ldt == null ? null : new java.util.Date(ldt.toDateTime().getMillis()));
        return d;
    }

    public static java.sql.Time toSQLTime(LocalTime lt) {
        // TODO - confirm this conversion always works, esp. across timezones
        java.sql.Time t = (lt == null ? null : new java.sql.Time(lt.getMillisOfDay()));
        return t;
    }

    public static Integer toIntegerMillis(LocalTime lt) {
        Integer i = (lt == null ? null : new Integer(lt.getMillisOfDay()));
        return i;
    }

    public static String toString(LocalTime lt) {
        String s = (lt == null ? null : lt.toString());
        return s;
    }

    // ///////////////////////////////////////////
    // From standard Java types to JodaTime types
    // ///////////////////////////////////////////

    public static DateMidnight toDateMidnight(java.sql.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d));
        return dm;
    }

    public static DateMidnight toDateMidnight(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d));
        return dm;
    }

    public static DateMidnight toDateMidnight(java.sql.Date d, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d, DateTimeZone.forID(timeZoneID)));
        return dm;
    }

    public static DateMidnight toDateMidnight(java.util.Date d, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateMidnight dm = (d == null ? null : new DateMidnight(d, DateTimeZone.forID(timeZoneID)));
        return dm;
    }

    public static DateTime toDateTime(java.sql.Timestamp ts) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (ts == null ? null : new DateTime(ts));
        return dt;
    }

    public static DateTime toDateTime(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (d == null ? null : new DateTime(d));
        return dt;
    }

    public static DateTime toDateTime(java.sql.Timestamp ts, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (ts == null ? null : new DateTime(ts, DateTimeZone.forID(timeZoneID)));
        return dt;
    }

    public static DateTime toDateTime(java.util.Date d, String timeZoneID) {
        // TODO - confirm this conversion always works, esp. across timezones
        DateTime dt = (d == null ? null : new DateTime(d, DateTimeZone.forID(timeZoneID)));
        return dt;
    }

    public static LocalDate toLocalDate(java.sql.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDate ld = (d == null ? null : LocalDate.fromDateFields(d));
        return ld;
    }

    public static LocalDate toLocalDate(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDate ld = (d == null ? null : LocalDate.fromDateFields(d));
        return ld;
    }

    public static LocalDate toLocalDate(String s) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDate ld = (s == null ? null : localDateFormatter.withZone(DateTimeZone.UTC).parseDateTime(s).toLocalDate());
        return ld;
    }
    
    public static LocalDateTime toLocalDateTime(java.sql.Timestamp ts) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDateTime ldt = (ts == null ? null : LocalDateTime.fromDateFields(ts));
        return ldt;
    }

    public static LocalDateTime toLocalDateTime(java.util.Date d) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalDateTime ldt = (d == null ? null : LocalDateTime.fromDateFields(d));
        return ldt;
    }

    public static LocalTime toLocalTime(java.sql.Time t) {
        // TODO - confirm this conversion always works, esp. across timezones
        LocalTime lt = (t == null ? null : new LocalTime(t, DateTimeZone.UTC));
        return lt;
    }

    public static LocalTime toLocalTime(Integer i) {
        LocalTime lt = (i == null ? null : LocalTime.fromMillisOfDay(i));
        return lt;
    }

    public static LocalTime toLocalTime(String s) {
        LocalTime lt = (s == null ? null : new LocalTime(s));
        return lt;
    }

    // ///////////////////////////////////////////
    // Tests
    // ///////////////////////////////////////////

    public static void main(String[] args) {
        test_dateMidnight();
        System.out.println(" ");
        test_dateMidnight_tz();
        System.out.println(" ");
        test_dateTime();
        System.out.println(" ");
        test_dateTime_tz();
        System.out.println(" ");
        test_localDate();
        System.out.println(" ");
        test_localDate_tz();
        System.out.println(" ");
        test_localDateTime();
        System.out.println(" ");
        test_localDate_shift_java_tz();
        System.out.println(" ");
        test_localDate_shift_joda_tz();
        System.out.println(" ");
        test_localTime_as_integer();
        System.out.println(" ");
        test_localTime_as_string();
        System.out.println(" ");
    }

    public static DateTimeZone losAngeles = DateTimeZone.forID("America/Los_Angeles");
    public static DateTimeZone brisbane = DateTimeZone.forID("Australia/Brisbane");
    public static DateTimeZone perth = DateTimeZone.forID("Australia/Perth");

    public static void test_dateMidnight() {
        System.out.println("Test DateMidnight");
        DateMidnight dm1 = new DateMidnight();
        java.sql.Date d = toSQLDate(dm1);
        DateMidnight dm2 = toDateMidnight(d);
        System.out.println("DateMidnight 1 = " + dm1);
        System.out.println("Date           = " + d);
        System.out.println("DateMidnight 2 = " + dm2);
        if (!dm2.equals(dm1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_dateMidnight_tz() {
        System.out.println("Test DateMidnight with timezone");
        DateMidnight dm1 = new DateMidnight(losAngeles);
        java.sql.Date d = toSQLDate(dm1);
        String tzID = toTimeZoneID(dm1);
        DateMidnight dm2 = toDateMidnight(d, tzID);
        System.out.println("DateMidnight 1 = " + dm1);
        System.out.println("Date           = " + d);
        System.out.println("TimeZoneID     = " + tzID);
        System.out.println("DateMidnight 2 = " + dm2);
        if (!dm2.equals(dm1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_dateTime() {
        System.out.println("Test DateTime");
        DateTime dt1 = new DateTime();
        java.sql.Timestamp ts = toSQLTimestamp(dt1);
        DateTime dt2 = toDateTime(ts);
        System.out.println("DateTime 1 = " + dt1);
        System.out.println("Timestamp  = " + ts);
        System.out.println("DateTime 2 = " + dt2);
        if (!dt2.equals(dt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_dateTime_tz() {
        System.out.println("Test DateTime with timezone");
        DateTime dt1 = new DateTime(losAngeles);
        java.sql.Timestamp ts = toSQLTimestamp(dt1);
        String tzID = toTimeZoneID(dt1);
        DateTime dt2 = toDateTime(ts, tzID);
        System.out.println("DateTime 1 = " + dt1);
        System.out.println("Timestamp  = " + ts);
        System.out.println("TimeZoneID = " + tzID);
        System.out.println("DateTime 2 = " + dt2);
        if (!dt2.equals(dt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate() {
        System.out.println("Test LocalDate");
        LocalDate ld1 = new LocalDate();
        java.sql.Date d = toSQLDate(ld1);
        LocalDate ld2 = toLocalDate(d);
        System.out.println("LocalDate 1 = " + ld1);
        System.out.println("Date        = " + d);
        System.out.println("LocalDate 2 = " + ld2);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate_tz() {
        System.out.println("Test LocalDate with timezone");
        DateMidnight dm1 = new DateMidnight(losAngeles);
        LocalDate ld1 = new LocalDate(dm1);
        java.sql.Date d = toSQLDate(ld1);
        LocalDate ld2 = toLocalDate(d);
        System.out.println("DateTime 1  = " + dm1);
        System.out.println("LocalDate 1 = " + ld1);
        System.out.println("Date        = " + d);
        System.out.println("LocalDate 2 = " + ld2);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate_shift_java_tz() {
        System.out.println("Test LocalDate with shifted Java timezone");

        TimeZone originalTZ = TimeZone.getDefault();
        TimeZone losAngelesTZ = TimeZone.getTimeZone("America/Los_Angeles");

        TimeZone.setDefault(losAngelesTZ);
        LocalDate ld1 = new LocalDate();
        System.out.println("ld1 LocalDate()   = " + ld1 + " when default TZ = " + TimeZone.getDefault());

        java.sql.Date d = toSQLDate(ld1);
        System.out.println("d toSQLDate(ld1)  = " + d + " when default TZ = " + TimeZone.getDefault());
        TimeZone.setDefault(originalTZ);
        System.out.println("d toSQLDate(ld1)  = " + d + " when default TZ = " + TimeZone.getDefault());

        LocalDate ld2 = toLocalDate(d);
        System.out.println("ld2 toLocalDate(d) = " + ld2 + " when default TZ = " + TimeZone.getDefault());

        TimeZone.setDefault(originalTZ);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localDate_shift_joda_tz() {
        System.out.println("Test LocalDate with shifted JodaTime timezone");
        DateTimeZone originalTZ = DateTimeZone.getDefault();
        DateTimeZone losAngelesTZ = DateTimeZone.forID("America/Los_Angeles");

        DateTimeZone.setDefault(losAngelesTZ);
        LocalDate ld0 = new LocalDate(losAngelesTZ);
        System.out.println("ld0 LocalDate(losAngelesTZ) = " + ld0 + " when default TZ = " + DateTimeZone.getDefault());

        DateTimeZone.setDefault(losAngelesTZ);
        LocalDate ld1 = new LocalDate();
        System.out.println("ld1 LocalDate()             = " + ld1 + " when default TZ = " + DateTimeZone.getDefault());

        java.sql.Date d0 = toSQLDate(ld1);
        System.out.println("d0 toSQLDate(ld0)           = " + d0 + " when default TZ = " + DateTimeZone.getDefault());
        java.sql.Date d1 = toSQLDate(ld1);
        System.out.println("d1 toSQLDate(ld1)           = " + d1 + " when default TZ = " + DateTimeZone.getDefault());
        DateTimeZone.setDefault(originalTZ);
        System.out.println("d1 toSQLDate(ld1)           = " + d1 + " when default TZ = " + DateTimeZone.getDefault());

        DateTimeZone.setDefault(originalTZ);
        LocalDate ld2 = toLocalDate(d1);
        System.out.println("ld2 toLocalDate(d1)         = " + ld2 + " when default TZ = " + DateTimeZone.getDefault());

        DateTimeZone.setDefault(originalTZ);
        if (!ld2.equals(ld1)) {
            throw new IllegalStateException();
        }
    }

    // public static void test_localDate_tz() {
    // System.out.println("Test LocalDate with different timezones");
    // LocalDate ld1 = new LocalDate(losAngeles);
    // java.sql.Date d1 = toSQLDate(ld1);
    // LocalDate ld2 = new LocalDate(perth);
    // java.sql.Date d2 = toSQLDate(ld2);
    // LocalDate ld3 = new LocalDate();
    // java.sql.Date d3 = toSQLDate(ld3);
    // System.out.println("LocalDate 1 = " + ld1);
    // System.out.println("Date        = " + d);
    // System.out.println("LocalDate 2 = " + ld2);
    // if (!ld2.equals(ld1)) {
    // throw new IllegalStateException();
    // }
    // }

    public static void test_localDateTime() {
        System.out.println("Test LocalDateTime");
        LocalDateTime ldt1 = new LocalDateTime();
        java.sql.Timestamp ts = toSQLTimestamp(ldt1);
        LocalDateTime ldt2 = toLocalDateTime(ts);
        System.out.println("LocalDateTime 1 = " + ldt1);
        System.out.println("Timestamp       = " + ts);
        System.out.println("LocalDateTime 2 = " + ldt2);
        if (!ldt2.equals(ldt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localTime_as_integer() {
        System.out.println("Test LocalTime as Integer");
        LocalTime lt1 = new LocalTime();
        Integer i = toIntegerMillis(lt1);
        LocalTime lt2 = toLocalTime(i);
        System.out.println("LocalTime 1 = " + lt1);
        System.out.println("Integer     = " + i);
        System.out.println("LocalTime 2 = " + lt2);
        if (!lt2.equals(lt1)) {
            throw new IllegalStateException();
        }
    }

    public static void test_localTime_as_string() {
        System.out.println("Test LocalTime as String");
        LocalTime lt1 = new LocalTime();
        String t = toString(lt1);
        LocalTime lt2 = toLocalTime(t);
        System.out.println("LocalTime 1 = " + lt1);
        System.out.println("String      = " + t);
        System.out.println("LocalTime 2 = " + lt2);
        if (!lt2.equals(lt1)) {
            throw new IllegalStateException();
        }
    }

}