Reputation: 3
Here is a simple example of a problem I get often when working with CompoundPropertyModel in Wicket:
This is my entity Item
with its model ItemModel
and controller ItemController
:
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
private long createdTimestamp;
public Item(long createdTimestamp) {
super();
this.createdTimestamp = createdTimestamp;
}
public long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(long createdTimestamp) {
this.createdTimestamp = createdTimestamp;
}
}
public class ItemModel extends CompoundPropertyModel<Item> {
private static final long serialVersionUID = 1L;
public ItemModel(Item object) {
super(object);
}
}
public class ItemController {
public static int getDuration(Item item) {
return Days.daysBetween(new DateTime(item.getCreatedTimestamp()), new DateTime()).getDays();
}
}
Let's say I want to display createdTimestamp and duration on a panel. If I do something like this:
public class ItemPanel extends GenericPanel<Item> {
private static final long serialVersionUID = 1L;
public ItemPanel(String id, ItemModel itemModel) {
super(id);
setModel(itemModel);
add(new Label("createdTimestamp"));
add(new Label("duration"));
}
}
Label createdTimestamp would be diplayed as a long value, because that's what getCreatedTimestamp() method returns and panel would throw an exception, because there is no getDuration() method implemented in class Item
.
One possible solution for this is to create a wrapper for the class Item
, which could be called ItemView
:
public class ItemView implements Serializable {
private static final long serialVersionUID = 1L;
private Item item;
public ItemView(Item item) {
super();
this.item = item;
}
public Date getCreatedTimestamp() {
return new Date(item.getCreatedTimestamp());
}
public int duration() {
return ItemController.getDuration(item);
}
}
It would work, but I don't like this solution, because creating and using wrapper classes for all of your entities can be very time consuming, if you have many of them and they have many fields.
Item
has already wrapper, which is called ItemModel
. That's why I would like to create getter methods in this class and force the model to use them.
Here is an example of what I mean:
public class ItemModel extends CompoundPropertyModel<Item> {
private static final long serialVersionUID = 1L;
public ItemModel(Item object) {
super(object);
}
public Date getCreatedTimestamp() {
return new Date(getObject().getCreatedTimestamp());
}
public int duration() {
return ItemController.getDuration(getObject());
}
}
But in this case the getter methods would be ignored by the model. So far I haven't found out how to force the model to use them. Is there any way to do it?
Upvotes: 0
Views: 1859
Reputation: 5681
CompoundPropertyModel is very useful when you have a direct mapping between components and properties. But sometimes it's just easier to use an AROM:
add(new Label("createdTimestamp", new AbstractReadOnlyModel<Date>() {
public Date getObject() {
return new Date(getModelObject().getCreatedTimestamp());
}
});
Note that with Wicket 8 you can write this much shorter:
add(new Label("createdTimestamp", () -> new Date(getModelObject().getCreatedTimestamp())));
(Since we're actually just converting from Long to Date it might be better to use a converter instead.)
Item has already wrapper, which is called ItemModel. That's why I would like to create getter methods in this class and force the model to use them
A model should never have additional methods besides those defined in IModel - anything you add there will not be called by Wicket, but you'll end up with unwanted dependencies between your components (like your panel requiring an ItemModel as argument).
creating and using wrapper classes for all of your entities can be very time consuming
I'm wondering why you're defining ItemModel then. It would be better to just accept any model in your constructor:
public ItemPanel(String id, IModel<Item> itemModel) {
super(id, new CompoundPropertyModel<Item>(itemModel);
}
Why should the caller know that your panel uses a CompoundPropertyModel inside?
Follow up:
There are legitimate reasons to create your own model implementations. Take a look at those brought along with Wicket:
They all have a technical reason to exist. You will create your own model implementation for your own technical requirements, e.g.
new EntityModel(uuid, clazz)
new PropertyInYourGeneratedLegacyObjectModel(order, R.id.ORDER_NUMBER)
I've seen other model implementations that I find questionable. The following two often have additional methods, which could be a smell that something is wrong:
new TransactionalModel(wrapped).commit()
new AsyncModel(wrapped).startAsync()
People have advocated creating reusable models related to your domain - e.g. OrderSumModel - but IMHO this has failed. Sooner or later domain logic will be contained in models and you'll see the following pattern emerge:
BigDecimal sum = new OrderSumModel(order).getObject();
Most of the time I'm just writing anonymous inner classes when I need something special:
new Label("sum", new AbstractReadOnlyModel() {
public BigDecimal getObject() {
return service.calculateSum(getModelObject());
}
});
As noted above, with Java 8 and Wicket 8 this becomes much easier to write:
new Label("sum", () -> service.calculateSum(getModelObject()));
As always there are exceptions:
When I need a model with complicated domain logic (e.g. OrderSumModel), I keep it as a nested inner class. As soon as I need it from more than one location, I think again.
Upvotes: 2
Reputation: 3239
The reason that your model isn't forced to use those methods is that your ItemModel Class isn't a child class of ItemView. In the class declaration for Item model make it extend ItemView. That's when the getter methods of the parent class will be overriden.
Update:
I just realized that ItemModel already extends a different class. In Java, a class can only extend 1 parent class. This is tricky to get around. One way is to make ItemView an inner class of CompoundPropertyModel and then ItemModel extends CompoundPropertyModel. That should work. If you have trouble google "make java class extend multiple classes" and there are good results.
Upvotes: 0