You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Next »

What are Wicket Models?

In Wicket like in other view frameworks a model provides the data a component should show and/or edit. How the model-concept is implemented and used is different between different view frameworks. Ie in Swing, you have a number of component specific model-interfaces or in Struts the model is represented by a bean and there is no explicit model-interface.

In Wicket there is only one generic model interface (wicket.model.IModel). Each component in wicket takes an instance of this one and only IModel interface. Ie a Label, a TextField, a ListView each take an IModel instance.

All an IModel does is provide a way to get and set a value for a component - nothing more. What this value is depends on the component. Ie. for a Label it must be something, which can be converted to a String. For a ListView it must be a java.util.List. An IModel just binds a wicket component to a component-specific value. Outside of wicket this concept is therefore often called a binding. You could also say Wicket comes with its integrated binding-framework.

This has a number of advantages:

  • There are default IModel implentations you can use on all different components (you will see later). Ie there is a powerful PropertyModel to work on beans and more.
  • You do not have to implement an extra interface or helper class for each different component as in Swing. Especially for the most often used components like Labels and TextFields you can easily bind to a bean property.
  • If you do not wish to implement an IModel you can provide the demanded value for the component directly and the component will do a default IModel binding internally.
  • And while you are not tied to using beans as your models as in Struts, you may still easily use beans.

Finally, for web-apps, an IModel has another big advantage. If you extend your model from wicket.model.AbstractDetachableModel the model automatically gets called at the end of the servlet-request to allow it to unload (detach) its value. It is also called to allow it to load (attach) its value the first time the value is accessed in the current servlet-request. When the IModel is set on a component this happens automatically without any easy to forget explicit registration. This way you can transparently use large values (ie Lists) without much concern whether they would require too much memory in the Session. Nearly just like in a client-app.

Simple Models

The HelloWorld example program demonstrates the simplest model type in
Wicket:

public class HelloWorld extends WicketExamplePage
{
	public HelloWorld()
	{
		add(new Label("message", "Hello World!"));
	}
}

The first parameter to the Label component added in the constructor is
the Wicket id, which associates the Label with a tag in the
HelloWorld.html markup file:

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns:wicket='http://wicket.sourceforge.net/wicket'>
<head>
	<title>Wicket Examples - helloworld</title>
	<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
	<span wicket:id="mainNavigation"/>
	<span wicket:id="message">Message goes here</span>
</body>
</html>

The second parameter to Label is the model for the Label, which
provides content which replaces any text inside the <span> tag that
the Label is attached to. The model being passed to the Label
constructor above is apparently a String. But if we look inside the
Label constructor we see that this is merely a convenience and that
the Label constructor wraps its String argument in a Model object like
this:

	public Label(final String id, String label)
	{
		this(id, new Model(label));
	}

If we look up the inheritance hierarchy for Label, we see that Label
extends WebComponent, which in turn extends Component. Each of these
superclasses has only two constructors. One Component constructor
takes a String id:

	public Component(final String id)
	{
		setId(id);
	}

And the other takes a String id and an IModel:

	public Component(final String id, final IModel model)
	{
		setId(id);
		setModel(model);
	}

IModel is the interface that must be implemented by all Wicket model
objects. It looks like this:

public interface IModel extends IDetachable
{
	public Object getNestedModel();
	public Object getObject(final Component component);
	public void setObject(final Component component, final Object object);
}

And the base interface IDetachable looks like this:

public interface IDetachable extends Serializable
{
	public void detach();
}

These details are not especially interesting to a beginning model
user, so we will come back to this later. What we need to know right
now is that Model extends AbstractModel, which implements IModel. So a
Model is an IModel and therefore it can be passed to the
Component(String, IModel) constructor we saw a moment ago.

If we take a peek inside Model, we'll see that the Model object is
simply a wrapper that holds a Serializable object:

	private Serializable object;

Model's object value is Serializable in order to support deployment of
web applications in clustered environments. Note also that since
IModel extends IDetachable, which extends Serializable, all IModel
wrapper classes are themselves Serializable. So all Wicket models are
therefore serializable, which means that they can be replicated
between servers in a cluster. Don't worry if you don't fully
understand this. We will talk more about clustering below.

Property Models

The PropertyModel class allows you to create a model that accesses a
particular property of its nested model object at runtime. This
property is accessed using a simple expression language with dot notation
(e.g. 'name' means property 'name', and 'person.name' means property name
of property person). The simplest PropertyModel constructor is:

	public PropertyModel(final Object modelObject, final String expression)

which takes a model object and a property expression. When the property model
is asked for its value by the framework, it will use the property expression to
access the model object's property. For example, if we have a Java Bean or
"POJO" (Plain Old Java Object) like this:

class Person
{
	private String name;
	
	Person(String name)
	{
		this.name = name;
	}

	String getName()
	{
		return name;
	}
}

then the property expression "name" can be used to access the "name"
property of any Person object via the getName() getter method.
Nested property
expressions are possible as well. You can access
sub-properties via reflection using a dotted path notation, which
means the property expression "person.name" is equivalent to calling
getPerson().getName() on the given model object.

A second contructor on PropertyModel is available for models that
wish to access properties involving type conversions:

	public PropertyModel(final Object modelObject, final String expression, Class propertyType)

Although the property models will do automatic type conversions for you, it may not
always do the kind of conversion you desire. This alternative
constructor creates a property model that forces a particular
conversion to occur. For example, you may wish to access a String or
Object property of something as an Integer. Constructing a
PropertyModel with new PropertyModel(object, "myString",
Integer.class) would force the property to be converted to and from an
Integer on get/set operations.

Compound Property Models

If you were paying close attention earlier, you may have wondered why
Components, which generally must have a model, can be constructed with
only an id, omitting any model. There are two reasons for this.

The first reason to construct a Component with no model is that it is
sometimes impossible to have a model ready for the call to
super(String, IModel) in the Component's constructor. In this case,
the Component can be constructed without a model, and the model can
be later set with a call to setModel().

The second reason to construct a Component with no model is that some
Components are actually bound to their models at runtime. When an
attempt is made to retrieve the model for a Component which does not
yet have one, instead of failing and returning null, the getModel()
method in Component calls the method initModel(), which gives the Component
an opportunity to lazy-initialize, returning a model on-the-fly.

This mechanism opens up all kinds of possibilities for model
initialization. One is particularly convenient and efficient lazy
model initialization strategy is employed by the CompoundPropertyModel
and BoundCompoundPropertyModel classes to allow containers to share
their models with children. By sharing model objects like this, fewer
objects are created, which saves memory, but more importantly, it
makes replication of models much cheaper in a clustered environment.

To use a CompoundPropertyModel, you simply set one as the model for
any container. However, a very typical place to install a
CompoundPropertyModel is on a Form or Page. This is natural, because
the compound model itself is usually the model that is being displayed
by the Page or edited by the Form.

To see a CompoundPropertyModel in action, take a look at the FormInput
example. In the InputForm nested class in FormInput.java, the Form
is constructed like this:

	public InputForm(String name, IFeedback feedback)
	{
		super(name, new CompoundPropertyModel(new FormInputModel()), feedback);
		...
	}

If we look at the next few lines of the Form's constructor, we see
statements like:

		add(new RequiredTextField("stringProperty"));
		add(new RequiredTextField("integerProperty", Integer.class));
		add(new RequiredTextField("doubleProperty", Double.class));

these statements are adding FormComponents to the Form. But they are
not explicitly attached to any model. Instead, they will inherit the
CompoundPropertyModel for the form at runtime. When the framework needs
the model for a RequiredTextField, it will call getModelObjectAsString().
This method retrieves the text field's model by calling getModel(), which
is implemented like this:

	public IModel getModel()
	{
		if (model == null)
		{
			this.model = initModel();
		}
		return model;
	}

Since the RequiredTextField has no model, the initModel() method will
be called, resulting in a search up the containment hierarchy for a
CompoundPropertyModel that can be inherited by the model-less child:

	protected IModel initModel()
	{
		for (Component current = getParent(); current != null; current = current.getParent())
		{
			final IModel model = current.getModel();
			if (model instanceof CompoundPropertyModel)
			{
				return model;
			}
		}
		return null;
	}

The text field will discover the CompoundPropertyModel for the Form
and set that as its own model. So getModel() on the text field
ultimately returns the model of the Form.

Next, the nested model object is retrieved from the model by passing
in the Component (in this case the RequiredTextField) as a parameter
to IModel.getObject(Component). This is the magic that allows the
CompoundPropertyModel to retrieve the right value from the Form's
model for the Component that is asking for it:

	final Object modelObject = model.getObject(this);

The retrieval is done by using the name of the Component as the property
expression to access the model. In the case of this component

		add(new RequiredTextField("stringProperty"));

the getStringProperty() method would be called on the Form's model.

The model's object value is available in this way to the component for
processing. In the case of the stringProperty text field component, it
will convert the value to a String. In the case of other
RequiredTextFields in the FormInput form, a conversion may occur based
on the third parameter to the Component. For example, this component:

	add(new RequiredTextField("integerProperty", Integer.class));

will convert the contents of the text field to and from an Integer
value.

In the event that you need more flexibility or property expression power in your
compound models, you can use the BoundCompoundPropertyModel. This
class provides three methods you can call on the container's model
object to bind a given child Component to a specific property
and/or type conversion:

	public Component bind(final Component component, final String propertyExpression)
	public Component bind(final Component component, final Class type)
	public Component bind(final Component component, final String propertyExpression, final Class type)

To see how this might be used, suppose that the stringProperty value
needed to be bound to the property expression "person0.stringProperty".
In the FormInput constructor, we'd say something like this:

	super(name, feedback);
	final BoundCompoundPropertyModel formModel = new BoundCompoundPropertyModel(new FormInputModel());
	setModel(formModel);
	add(formModel.bind(new RequiredTextField("stringProperty"), "person[0].stringProperty"));

The Form is constructed without a model. We then create the Form's
model as a BoundCompoundPropertyModel, set it as the Form's model and
finally use it to bind the RequiredTextField to the desired property
expression.

String Resource Models

Localization of resources in Wicket is supported by the Localizer
class. This provides a very convenient way to retrieve and format application
and component specific resources according to the Locale that the
user's Session is running under. In many cases, it will be sufficient
to simply retrieve a localized String in this way, but if a formatted, localized
string is desired as a model, a StringResourceModel may be used.
StringResourceModels have a resource key (for looking up the string
resource in a property file), a Component, an optional model and an
optional array of parameters that can be passed to the Java
MessageFormat facility. The constructors look like this:

	public StringResourceModel(final String resourceKey, final Component component,
			final IModel model)
	public StringResourceModel(final String resourceKey, final Component component,
			final IModel model, final Object[] parameters)

A very simple example of a StringResourceModel might be a Label
constructed like this:

	add(new Label("greetings", new StringResourceModel("label.greetings", this, null)));

where the resource bundle for the page contains an entry like this:

	label.greetings=Welcome!

By adding a model, we can use property expressions to interpolate properties from the
model into the localized string in the resource bundle. For example,
suppose we wanted to add the name from a User object to the greeting
above. We'd say:

	User user;

	...

	add(new Label("greetings", new StringResourceModel("label.greetings", this, user)));

and have a resource like:

	label.greetings=Welcome, ${user.name}!

where User has a getName() method exposing its "name" property.

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 a property expression and conversion type retrieved
from a subclass via abstract methods propertyExpression() and
propertyType(). The simple PropertyModel class just returns the property
expression and type it was constructed with:

	protected String propertyExpression(Component component)
	{
		return expression;
	}

	protected Class propertyType(Component component)
	{
		return propertyType;
	}

While CompoundPropertyModel returns the name of the component as its
property expression:

	protected String propertyExpression(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 property expression for a component based on this information:

	protected String propertyExpression(final Component component)
	{
		final Binding binding = getBinding(component);
		return binding != null ? binding.propertyExpression : component.getId();
	}

	protected Class propertyType(final Component component)
	{
		final Binding binding = getBinding(component);
		return binding != null ? binding.type : null;
	}

category:Model

  • No labels