java.time.LocalDate 和 Wicket 7 DateTextField

java.time.LocalDate and Wicket 7 DateTextField

我正在尝试将 Wicket 组件 DateTextFieldjava.time.LocalDate 类型的 属性 连接起来。

按照 中的提示,我尝试实现一个包含 java.time.Localdate 属性 的自定义 CompoundPropertyModel。我正在使用 Wicket 7.6 和 JDK 8.

我是 Wicket 和 Java 泛型的新手。

这是我的代码:

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 与 LocalDate 属性

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
}    

正在创建组件

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

这些是错误消息:

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!

堆栈跟踪:

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.component(MarkupContainer.java:876)
     at org.apache.wicket.MarkupContainer.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.<init>(MyCustomerOverviewPage.java:95)

第 35 行是:

 if (actualModel.getObject() instanceof LocalDate)

已更新,答案更简洁

我最初的回答远远超出了你的实际问题,但直到后来我才完全意识到。这是一个更简洁的版本,但我会将原始版本放在底部。

您遇到的问题是因为我建议的幼稚扩展有一个严重缺陷。

CompoundPropertyModel 将尝试为 any child 没有模型的组件实例化模型。当您使用常规 CompoundPropertyModel 时,这不是问题;如果模型无关紧要,则永远不会尝试获取其 object。

这里发生的事情是模型的这个扩展不仅会为任何 model-less child 创建一个模型,而且还会尝试评估它以检查它的类型属性 是。

问题在于,您可能有 children 组件(例如链接),其中模型不一定会解析为任何内容。在此特定实例中,您似乎添加了一个 ID 为 "dialog" 的组件;即使它可能不一定需要模型,CompoundPropertyModel 仍会为它调用,并且它会尝试从底层 [=21] 中获取名为 "dialog" 的 属性 =] object。

因此,为了不遇到此问题,您需要防止您的扩展程序每次都评估模型。像

这样简单的东西
@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;
    }
}

希望对您有所帮助。


原回答

要了解您的代码为何不起作用,您需要了解 CompoundPropertyModel 的工作原理。

我将使用您的稍微修改过的代码片段来说明这一点

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

假设我们要向显示 CustomerUI 的 "activeDate" 属性 的 detailsDialog 添加标签。如果我们 而不是 使用 CompoundPropertyModel,那么为了实现这一点,我们必须执行如下操作:

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

CompoundPropertyModel 所做的是让我们绕过为组件提供模型并简单地做

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

然后当页面被渲染时,一个组件会检测到它没有模型,所以它给 parent 模型一个机会为它提供模型。

这就是 CompoundPropertyModel 发挥作用的地方。它会查看哪个组件请求提供模型并执行此操作。然而,它是如何做到的呢?嗯,很简单

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

因此,由于这个简单的逻辑片段,您可以绕过在 children 组件上实例化您自己的模型,并让 parent 的 [=18] 向它们提供模型=]. 但是 child 组件的 ID 必须引用存储在 CompoundPropertyModel 中的模型 object 的某些 属性。否则你会得到你得到的确切错误:

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

现在让我们看看您对 CompoundPropertyModel 的扩展。当组件请求为自己获取模型时,您的扩展会执行以下操作:

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

它的作用是调用 CompoundPropertyModel 的默认行为来实例化组件的模型,然后检测此模型 returns 是否是 LocalDate 的实例,并且包装该模型以便拥有一个 returns Date 的模型。

因为您的扩展仍然依赖于 CompoundPropertyModel 的默认行为,它仍将使用组件的 wicket ID 来确定内部模型中 属性 的名称。所以发生的事情是这样的:

  1. 您向 detailsDialog 添加了一个组件,其 wicket ID 是 "dialog"
  2. 在线actualModel = super.wrapOnInheritance(component); 你的扩展创建了一个 PropertyModel 来引用你的 CustomerUI 型号及其 属性 "dialog"(因为这是 提供的组件)
  3. 在线 if (actualModel.getObject() instanceof LocalDate) 您试图获取此模型的值。 但是,CustomerUI object 上不存在 属性 "dialog" 并且 所以抛出错误

所以最后你做对了一切。唯一的问题是您添加了一个 child 组件,其 wicket ID 没有引用有效的 属性.

这也说明了一个问题。我提供的实现是幼稚的。每当添加一个没有模型的组件时,这个扩展模型都会为其创建一个模型,而且它总是会尝试获取它的值。因此,如果您添加一个 child 甚至不应该有模型的组件,复合 属性 模型甚至会为这些组件启动。

所以更永久的解决方案是不涉及尝试获取模型的值。例如。你可以检查组件是否是 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;
    }
}

这将推迟查看模型,直到您确定您尝试实例化其模型的组件确实需要 LocalDate 模型。

根据 WiseTrees 的回答,我修改了 classes。尽管在 class MyLocalDateCompoundPropertyModel.

中有与类型转换相关的警告,但此解决方案有效

class MyLocalDateModel

package example.ui.models;

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

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

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

    @Override
    public Date getObject()
    {
        return MyDateTimeUtil.createDateFromLocalDate(localDateModel.getObject());
    }

    @Override
    public void setObject(Date object)
    {
        localDateModel.setObject(MyDateTimeUtil.createLocalDateFromDate(object));
    }

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

class MyWrappingLocalDateModel

package example.ui.models;

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

public class MyWrappingLocalDateModel extends MyLocalDateModel implements IWrapModel<Date>
{
    private static final long serialVersionUID = -8539316259078354206L;

    public MyWrappingLocalDateModel(IModel<LocalDate> localDateModel)
    {
        super(localDateModel);
    }

    @Override
    public IModel<Date> getWrappedModel()
    {
        return Model.of(super.getObject());
    }

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

class MyLocalDateCompoundPropertyModel

package example.ui.models;

import java.time.LocalDate;
import org.apache.wicket.Component;
import org.apache.wicket.extensions.markup.html.form.DateTextField;
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)
    // warning: type Parameter 'T' hides type parameter 'T'
    {
        IWrapModel<T> actualModel;
        actualModel = super.wrapOnInheritance(component);

        if ((component instanceof DateTextField) && (actualModel.getObject() instanceof LocalDate))
        {
            return (IWrapModel<T>) new MyWrappingLocalDateModel((IModel<LocalDate>) actualModel);
            // warning: unchecked cast
        }
        else
        {
            return actualModel;
        }

    }
}