Standard task when developing a web application: there is a data object, this data is required to be displayed (output in HTML). In
Apache Wicket, data for this is tied to components (which will be engaged in mapping) using models (implementing the
IModel interface).
Most likely, this publication will be read by those who are already in the know, but just in case: the main method from the IModel that interests us is:
T getObject();
Abstraction is simple and concise, but not everything is so simple in practice. Under the cut - a tale about how Java 8 helped defeat the verbosity and insecurity of standard approaches.
Data
Test data class: public class User implements Serializable { private final String name; private final int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } }
')
Attempt # 1: we wrote, we wrote ...
Here's how to approach the task:
public class AbstractReadOnlyModelPanel extends Panel { public AbstractReadOnlyModelPanel(String id, IModel<User> model) { super(id, model); add(new Label("name", new AbstractReadOnlyModel<String>() { @Override public String getObject() { return model.getObject().getName(); } })); add(new Label("age", new AbstractReadOnlyModel<Integer>() { @Override public Integer getObject() { return model.getObject().getAge(); } })); } }
Everything is cheap, reliable and practical: we create an anonymous subclass of AbstractReadOnlyModel. It works quickly, it looks clear. One problem - due to the use of that same anonymous class, the code is cumbersome: 6 lines per component is not a joke.
Attempt # 2: shoot in the direction of the leg
Now we will try
PropertyModel :
public class PropertyModelPanel extends Panel { public PropertyModelPanel(String id, IModel<User> model) { super(id, model); add(new Label("name", PropertyModel.of(model, "name"))); add(new Label("age", PropertyModel.of(model, "age"))); } }
Wow, much smaller. But in a barrel of honey there is a lot of tar:
- First and foremost, the compiler is not an assistant for us. You can specify a non-existent property, you can specify a property of the wrong type, you can make many more interesting errors.
- Secondly: reflection. Not critical, but its presence is not happy.
Lambda come on stage
Fortunately, Java 8 has long been released, and
lambdas along with
Method References are already in a hurry to help:
public class GetterModel<E, P> extends AbstractReadOnlyModel<P> { private final E entity; private final IModel<E> entityModel; private final IPropertyGetter<E, P> getter; private GetterModel(E entity, IModel<E> entityModel, IPropertyGetter<E, P> getter) { this.entity = entity; this.entityModel = entityModel; this.getter = getter; } public static <E, P> GetterModel<E, P> ofObject(E entity, IPropertyGetter<E, P> getter) { Objects.requireNonNull(entity, "Entity cannot be null"); Objects.requireNonNull(getter, "Getter cannot be null"); return new GetterModel<>(entity, null, getter); } public static <E, P> GetterModel<E, P> ofModel(IModel<E> entityModel, IPropertyGetter<E, P> getter) { Objects.requireNonNull(entityModel, "Entity model cannot be null"); Objects.requireNonNull(getter, "Getter cannot be null"); return new GetterModel<>(null, entityModel, getter); } @Override public P getObject() { return getter.getPropertyValue(getEntity()); } private E getEntity() { return entityModel != null ? entityModel.getObject() : entity; } }
public interface IPropertyGetter<E, P> { P getPropertyValue(E entity); }
Well, immediately an example, rewritten with this implementation of the model:
public class GetterModelPanel extends Panel { public GetterModelPanel(String id, IModel<User> model) { super(id, model); add(new Label("name", GetterModel.ofModel(model, User::getName))); add(new Label("age", GetterModel.ofModel(model, User::getAge))); } }
Almost as briefly as in the PropertyModel example, besides:
- type safe: the compiler will check that the type matches (unless, of course, using a more legible component than Label);
- much more secure in terms of typos: if you are sealed, the compiler will most likely catch it;
- reflection is not used.
Compared to the PropertyModel, however, there are some drawbacks:
- GetterModel is read-only, while PropertyModel allows you to write. Adding more and setter deprives the idea of elegance, in addition adds another source of errors (it is possible to specify a setter from one property, and a getter from another).
- PropertyModel allows you to access nested properties using expressions like "outerObject.itsProperty.propertyOfProperty".
But there is a nice bonus: an analogue of the magical possibility of using a PropertyModel as a data source and model, and the POJO is implemented without any magic: we simply added two factory methods (ofModel () and ofObject ()).
Links
- Apache Wicket Framework
- Java: Lambda Expressions
- Java: Method References