Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
320 views
in Technique[技术] by (71.8m points)

date - java.time.LocalDate and Wicket 7 DateTextField

I'm trying to connect a Wicket component DateTextField with a property of type java.time.LocalDate.

Following the hints in this answer, I've tried to implement a custom CompoundPropertyModel which contains a java.time.Localdate property. I'm using Wicket 7.6 and JDK 8.

I'm new to Wicket and Java generics.

This is my code:

Class LocaldateModel

import java.time.*;
import java.util.Date;
import org.apache.wicket.model.IModel;

public class LocalDateModel implements IModel<Date>
{
    private static final long serialVersionUID = 7262517323706786573L;
    private IModel<LocalDate> localDateModel;

    public LocalDateModel(IModel<LocalDate> localDateModel)
    {
        this.localDateModel = localDateModel;
    }

    @Override
    public Date getObject()
    {
        return Date.from(localDateModel.getObject().atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

    @Override
    public void setObject(Date object)
    {
        localDateModel.setObject(
                Instant.ofEpochMilli(object.getTime()).atZone(ZoneId.systemDefault()).toLocalDate());
    }

    @Override
    public void detach()
    {
        localDateModel.detach();
    }
}

Class LocalDateModelButAlsoWrapping

import java.time.Localdate;
import java.util.Date;

public class LocalDateModelButAlsoWrapping<T> extends LocalDateModel implements IWrapModel<Date>
{
    private static final long serialVersionUID = -8539316259078354206L;

    private IModel<Date> dateModel;

    public LocalDateModelButAlsoWrapping(IModel<LocalDate> localDateModel)
    {
        super(localDateModel);
        LocalDate localDate=localDateModel.getObject();
        dateModel.setObject(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
    }

    @Override
    public IModel<Date> getWrappedModel()
    {
        return dateModel;
    }

    @Override
    public void setObject(Date object)
    {
       dateModel.setObject(object);
    }
}

Class MyLocalDateCompoundPropertyModel

import java.time.Localdate;
import org.apache.wicket.Component;
import org.apache.wicket.model.*;

public class MyLocalDateCompoundPropertyModel<T> extends CompoundPropertyModel<T>
{
    private static final long serialVersionUID = 7112056989820105247L;

    public MyLocalDateCompoundPropertyModel(IModel<T> model)
    {
        super(model);
    }

    public MyLocalDateCompoundPropertyModel(T object)
    {
        super(object);
    }

    @Override
    public <T> IWrapModel<T> wrapOnInheritance(Component component)
    {
        IWrapModel<T> actualModel;
        actualModel = super.wrapOnInheritance(component);

        if (actualModel.getObject() instanceof LocalDate)
        {
            return new LocalDateModelButAlsoWrapping(actualModel);
        }
        else
        {
            return actualModel;
        }
    }
}

Class with LocalDate property

import java.time.Localdate;

public class CustomerUI implements Serializable
{
    private static final long serialVersionUID = -4071467669346190367L;

    private CustomerDTO customer;

    public LocalDate getActiveUntil()
    {
        return customer.getActiveUntil();
    }

    public void setActiveUntil(LocalDate activeUntil)
    {
        customer.setActiveUntil(activeUntil);
    }

    // more delegating getters and setters
}    

Creating the component

    detailsDialog = new MyCustomerDetailsDialog("createDialog",
            new MyLocalDateCompoundPropertyModel<CustomerUI>(Model.of(new CustomerUI())))

These are the error messages:

Last cause: Property could not be resolved for class: class example.ui.customer.CustomerUI expression: dialog WicketMessage: Can't instantiate page using constructor 'public example.ui.customer.MyCustomerOverviewPage()'. An exception has been thrown during construction!

Stacktrace:

org.apache.wicket.WicketRuntimeException: Property could not be resolved for class: class example.ui.customer.CustomerUI expression: dialog
     at org.apache.wicket.core.util.lang.PropertyResolver.getGetAndSet(PropertyResolver.java:393)
     at org.apache.wicket.core.util.lang.PropertyResolver.getObjectWithGetAndSet(PropertyResolver.java:355)
     at org.apache.wicket.core.util.lang.PropertyResolver.getObjectWithGetAndSet(PropertyResolver.java:261)
     at org.apache.wicket.core.util.lang.PropertyResolver.getValue(PropertyResolver.java:111)
     at org.apache.wicket.model.AbstractPropertyModel.getObject(AbstractPropertyModel.java:86)
     at example.ui.models.MyLocalDateCompoundPropertyModel.wrapOnInheritance(MyLocalDateCompoundPropertyModel.java:35)
     at org.apache.wicket.Component.initModel(Component.java:3841)
     at org.apache.wicket.Component.getDefaultModel(Component.java:1625)
     at org.apache.wicket.MarkupContainer$2.component(MarkupContainer.java:876)
     at org.apache.wicket.MarkupContainer$2.component(MarkupContainer.java:872)
     at org.apache.wicket.util.visit.Visits.visitChildren(Visits.java:144)
     at org.apache.wicket.util.visit.Visits.visitChildren(Visits.java:123)
     at org.apache.wicket.util.visit.Visits.visitChildren(Visits.java:192)
     at org.apache.wicket.MarkupContainer.visitChildren(MarkupContainer.java:983)
     at org.apache.wicket.MarkupContainer.setDefaultModel(MarkupContainer.java:871)
     at example.ui.customer.MyCustomerDetailsDialog.<init>(MyCustomerDetailsDialog.java:44)
     at example.ui.customer.MyCustomerOverviewPage$2.<init>(MyCustomerOverviewPage.java:95)

Line 35 is:

 if (actualModel.getObject() instanceof LocalDate)
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Updated, more succint answer

My original answer went way beyond what your actual problem was, but I didn't quite realise until afterwards. Here's a more succint version, but I'll keep the original around at the bottom.

The issue you're having is because the naive extension I suggested has one critical flaw.

CompoundPropertyModel will try to instantiate a model for any child component that does not have a model. When you use regular CompoundPropertyModel this is not an issue; where the model is irrelevant, its object is never attempted to be obtained.

What's happening here is that this extension of the model will not only create a model for any model-less child, but will also try to evaluate it for the sake of checking what the type of the property is.

The problem with that is, that you might have children components (e.g. links) where a model will not necessarily be resolved to anything. In this specific instance you seem to have added a component whose ID is "dialog"; even though it might not necessarily need a model, CompoundPropertyModel will still be invoked for it, and it'll try to get a property by the name of "dialog" from the underlying CustomerUI object.

So in order to not encounter this issue you need to prevent your extension from evaluating the model every time. Something simple like

@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
    IWrapModel<T> actualModel;
    actualModel = super.wrapOnInheritance(component);

    if (component instanceof DateTextField && actualModel.getObject() instanceof LocalDate)
    {
        return new LocalDateModelButAlsoWrapping(actualModel);
    }
    else
    {
        return actualModel;
    }
}

Hope it helps.


Original answer

To understand why your code is not working, you need to understand how CompoundPropertyModel works.

I'll use a slightly modified code snippet of yours to illustrate this

IModel<CustomerUI> customerUIModel = Model.of(new CustomerUI());
detailsDialog = new MyCustomerDetailsDialog("createDialog", new CompoundPropertyModel<CustomerUI>(customerUIModel));

Let's imagine we want to add a label to the detailsDialog that displays the "activeDate" property of the CustomerUI. If we were not using a CompoundPropertyModel, then in order to achieve that we'd have to do something like the following:

IModel<LocalDate> activeDateModel = PropertyModel(customerUIModel, "activeUntil");
detailsDialog.add(new Label("dialog", activeDateModel));

What CompoundPropertyModel does is lets us bypass providing a model to the component and doing simply

detailsDialog.add(new Label("activeDate"))

Then when the page is being rendered, a component will detect that it has no model and so it gives the parent models a chance to provide a model to it.

This is where CompoundPropertyModel would kick in. It looks at what component is requesting to have a model provided and does exactly that. However, how does it do that? Well, quite simply it does

return new PropertyModel(innerModel, component.getId())

And so because of this simple piece of logic you can bypass instantiating your own models on the children components, and have the models provided to them by the parent's CompoundPropertyModel. However the ID of the child component must be referring to some property of the model object that is stored in the CompoundPropertyModel. Otherwise you will get the exact error you're getting:

Property could not be resolved for class: class example.ui.customer.CustomerUI expression: dialog

Now let's look at your extension of the CompoundPropertyModel. When a component requests to get a model for itself, your extension does the following:

@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
    IWrapModel<T> actualModel;
    actualModel = super.wrapOnInheritance(component);

    if (actualModel.getObject() instanceof LocalDate)
    {
        return new LocalDateModelButAlsoWrapping(actualModel);
    }
    else
    {
        return actualModel;
    }
}

What it does it invokes the default behavior of the CompoundPropertyModel to instantiate a model for a component, and then it detects whether this model returns an instance of a LocalDate and wraps that model in order to have a model that returns Date instead.

Because your extension still relies on the default behavior of CompoundPropertyModel, it will still use the component's wicket ID to determine what the name of the property within the inner model is. And so what's happening is this:

  1. You added a component to your detailsDialog whose wicket ID is "dialog"
  2. On line actualModel = super.wrapOnInheritance(component); your extension creates a PropertyModel to referring to your CustomerUI model and its property "dialog" (as that's the ID of the component provided)
  3. On line if (actualModel.getObject() instanceof LocalDate) you attempt to get the value of this model. However, no property "dialog" exists on the CustomerUI object and so the error is thrown

So in the end you've done everything right. The only problem is that you added a child component whose wicket ID did not refer to a valid property.

Which also showcases a problem. The implementation I provided is naive. Whenever a component is added that does not have a model, this extended model will create a model for it but also it will always try to get its value. So if you add a child component which isn't even supposed to have a model, the compound property model will kick in even for those components.

So the more permanent solution would be something that does not involve trying to get the value of the model. E.g. you could check if the component is a DateTextField:

@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
    IWrapModel<T> actualModel;
    actualModel = super.wrapOnInheritance(component);

    if (component instanceof DateTextField && actualModel.getObject() instanceof LocalDate)
    {
        return new LocalDateModelButAlsoWrapping(actualModel);
    }
    else
    {
        return actualModel;
    }
}

This would defer looking at the model until after you established that the component whose model you're trying to instantiate really does need a LocalDate model.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...