|
Detachable Models
If you look at the inheritance hierarchy for types that implement IModel in
the Wicket core, you will notice that there are two root classes which
implement IModel directly: AbstractModel and AbstractDetachableModel.
AbstractModel is intended as a base class for all models which do not make use
of IModel's IDetachable interface. To remove the need to implement
IModel.IDetachable.detach, AbstractModel looks like this:
public abstract class AbstractModel implements IModel { public void detach() { } }
AbstractModel has a single subclass, Model, which we talked about at the
beginning of this article, which wraps a Serializable object.
All other model classes in Wicket are "detachable" and extend the base class
AbstractDetachableModel. A detachable model in Wicket is a model which can get
rid of a large portion of its state to reduce the amount of memory it takes up
and to make it cheaper to serialize when replicating it in a clustered
environment. When an object is in the detached state, it contains only some
very minimal non-transient state such as an object id that can be used to
reconstitute the object from a persistent data store. When a detached object
is attached, some logic in the object uses this minimal state to reconstruct
the full state of the object. This typically involves restoring fields from
persistent storage using a database persistence technology such as JDO or
Hibernate.
To make implementation of detachable models easy, AbstractDetachableModel
provides some basic inheritable logic for attaching and detaching models. The
basis for this logic is a transient boolean field which is true when the
object is in the attached state and false when it is in the detached state:
private transient boolean attached = false;
Which state a detachable object is in is available via:
public boolean isAttached() { return attached; }
Two methods, attach() and detach() (which implements the IDetachable interface
contract), use this boolean to call the abstract subclass methods onAttach()
and onDetach() only when required:
public final void attach() { if (!attached) { attached = true; onAttach(); } }
public final void detach() { if (attached) { attached = false; onDetach(); } }
protected abstract void onAttach();
protected abstract void onDetach();
The implementations of IModel.getObject(Component) and
IModel.setObject(Component) automatically call attach() before delegating
responsibility to abstract methods:
public final Object getObject(final Component component) { attach(); return onGetObject(component); }
public final void setObject(final Component component, final Object object) { attach(); onSetObject(component, object); }
protected abstract Object onGetObject(final Component component);
protected abstract void onSetObject(final Component component, final Object object);
All of this very neatly encapsulates what it means to be a detachable object
in Wicket. If you implement these abstract methods:
public abstract Object getNestedModel(); protected abstract void onAttach(); protected abstract void onDetach(); protected abstract Object onGetObject(final Component component); protected abstract void onSetObject(final Component component, final Object object);
you will immediately have a functioning detachable model that shrinks and then
restores its state when replicated in a cluster.
There are two sub-types of AbstractDetachableModels. The first is
AbstractPropertyModel and the second is AbstractReadOnlyDetachableModel. The
latter is very simple and just adds this override to AbstractDetachableModel:
protected final void onSetObject(final Component component, final Object object) { throw new UnsupportedOperationException("Model " + getClass() + " does not support setObject(Object)"); }
The StringResourceModel we talked about above is an
AbstractReadOnlyDetachableModel since resource strings are read-only in
nature.
AbstractPropertyModel provides implementation details for the PropertyModel,
CompoundPropertyModel and BoundCompoundPropertyModel classes we discussed
above by implementing onGetObject() and
onSetObject() using an OGNL expression and conversion type retrieved from a
subclass via abstract methods ognlExpression() and propertyType(). The simple
PropertyModel class just returns the OGNL expression and type it was
constructed with:
protected String ognlExpression(Component component) { return expression; }
protected Class propertyType(Component component) { return propertyType; }
While CompoundPropertyModel returns the name of the component as its OGNL
expression:
protected String ognlExpression(final Component component) { return component.getId(); }
protected Class propertyType(final Component component) { return null; }
Finally, BoundCompoundPropertyModel stores a set of bindings internally and
returns the OGNL expression for a component based on this information:
protected String ognlExpression(final Component component) { final Binding binding = getBinding(component); return binding != null ? binding.ognlExpression : component.getId(); }
protected Class propertyType(final Component component) { final Binding binding = getBinding(component); return binding != null ? binding.type : null; }
LoadableDetachableModel
A model that makes working with custom detachable models a breeze is
LoadableDetachableModel. LoadableDetachableModel holds a temporary, transient
model object, that is set on attaching by calling abstract method 'load', and
that will be reset/set to null on detaching.
In the following example, we load objects (venues) on attachement.
LoadableDetachableModel venueListModel = new LoadableDetachableModel() { protected Object load() { return getVenueDao().findVenues(); } };
During the handling of the request cycle, the list of venues we found by
calling 'findVenues' is returned when getObject is called on the
venueListModel. When detached, the model representation is cleaned up.
Special purpose models
Some components need specialized models for their workings. Or, as is the case
with components that inherit from AbstractChoice, they need more than one model.
Currently, these specialized models are IChoiceList and FileUpload. You can find information
on them in the JavaDocs of the components that use them, or you can take a
look at the examples.
|