<!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   
     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 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.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();
        }
    }
}