Chapter 58. Seam Remoting - Model API

58.1. Introduction

The Model API builds on top of Seam Remoting's object serialization features to provide a
component-based approach to working with a server-side object model, as
opposed to the RPC-based approach provided by the standard Remoting API.
This allows a client-side representation of a server-side object graph to be modified ad hoc
by the client, after which the changes made to the objects in the graph can be
applied to the corresponding server-side objects. When applying the
changes the client determines exactly which objects have been modified by recursively walking
the client-side object tree and generating a delta by comparing the original property values
of the objects with their new property values.

This approach, when used in conjunction with the extended persistence context provided by Seam
elegantly solves a number of problems faced by AJAX developers when working remotely with
persistent objects. A persistent, managed object graph can be loaded at the start of
a new conversation, and then across multiple requests the client can fetch the objects,
make incremental changes to them and apply those changes to the same managed objects after
which the transaction can be committed, thereby persisting the changes made.

One other useful feature of the Model API is its ability to expand a model.
For example, if you are working with entities with lazy-loaded associations it is usually not a good idea
to blindly fetch the associated objects (which may in turn themselves contain associations
to other entities, ad nauseum), as you may inadvertently end up fetching the bulk of your database.
Seam Remoting already knows how to deal with lazy-loaded associations by automatically excluding
them when marshalling instances of entity beans, and assigning them a client-side value of
undefined (which is a special JavaScript value, distinct from null).
The Model API goes one step further by giving the client the option of manipulating the associated objects
also. By providing an expand operation, it allows for the initialization of a
previously-uninitialized object property (such as a lazy-loaded collection), by dynamically "grafting"
the initialized value onto the object graph. By expanding the model in this way,
we have at our disposal a powerful tool for building dynamic client interfaces.

58.2. Model Operations

For the methods of the Model API that accept action parameters, an instance of
Seam.Action should be used. The constructor for
Seam.Action takes no parameters:

var action = new Seam.Action();

The following table lists the methods used to define the action. Each of the following methods
return a reference to the Seam.Action object, so methods can be chained.

Table 58.1. Seam.Action method reference

Method

Description

setBeanType(beanType)

Sets the class name of the bean to be invoked.

beanType - the fully qualified class name of the bean type to be invoked.

setQualifiers(qualifiers)

Sets the qualifiers for the bean to be invoked.

qualifiers - a comma-separated list of bean qualifier names.
The names may either be the simple or fully qualified names of the qualifier classes.

setMethod(method)

Sets the name of the bean method.

method - the name of the bean method to invoke.

addParam(param)

Adds a parameter value for the action method. This method should be called once for
each parameter value to be added, in the correct parameter order.

param - the parameter value to add.

The following table describes the methods provided by the Seam.Model object. To work with
the Model API in JavaScript you must first create a new Model object:

var model = new Seam.Model();

Table 58.2. Seam.Model method reference

Method

Description

addBean(alias, bean, qualifiers)

Adds a bean value to the model. When the model is fetched, the value of the specified bean
will be read and placed into the model, where it may be accessed by using the
getValue() method with the specified alias.

Can only be used before the model is fetched.

alias - the local alias for the bean value.

bean - the name of the bean, either specified by the @Named
annotation or the fully qualified class name.

qualifiers (optional) - a list of bean qualifiers.

addBeanProperty(alias, bean, property, qualifiers)

Adds a bean property value to the model. When the model is fetched, the value of the specified
property on the specified bean will be read and placed into the model, where it may be accessed
by using the getValue() method with the specified alias.

bean - the name of the bean, either specified by the @Named
annotation or the fully qualified class name.

property - the name of the bean property.

qualifiers (optional) - a list of bean qualifiers. This parameter (and any
after it) are treated as bean qualifiers.

fetch(action, callback)

Fetches the model - this operation causes an asynchronous request to be sent to the server.
The request contains a list of the beans and bean properties (set by calling the
addBean() and addBeanProperty() methods) for which values
will be returned. Once the response is received, the callback method (if specified) will be
invoked, passing in a reference to the model as a parameter.

A model should only be fetched once.

action (optional) - a Seam.Action instance representing
the bean action to invoke before the model values are read and stored in the model.

callback (optional) - a reference to a JavaScript function that will be
invoked after the model has been fetched. A reference to the model instance is passed to
the callback method as a parameter.

getValue(alias)

This method returns the value of the object with the specified alias.

alias - the alias of the value to return.

expand(value, property, callback)

Expands the model by initializing a property value that was previously uninitialized. This
operation causes an asynchronous request to be sent to the server, where the uninitialized
property value (such as a lazy-loaded collection within an entity bean association) is
initialized and the resulting value is returned to the client. Once the response is received,
the callback method (if specified) will be invoked, passing in a reference to the model as a
parameter.

value - a reference to the value containing the uninitialized property
to fetch. This can be any value within the model, and does not need to be a "root" value
(i.e. it doesn't need to be a value specified by addBean() or
addBeanProperty(), it can exist anywhere within the object graph.

property - the name of the uninitialized property to be initialized.

callback (optional) - a reference to a JavaScript function that will be
invoked after the model has been expanded. A reference to the model instance is passed to
the callback method as a parameter.

applyUpdates(action, callback)

Applies the changes made to the objects contained in the model. This method causes an
asynchronous request to be sent to the server containing a delta consisting of
a list of the changes made to the client-side objects.

action (optional) - a Seam.Action instance representing
a bean method to be invoked after the client-side model changes have been applied to their
corresponding server-side objects.

callback (optional) - a reference to a JavaScript function that will be
invoked after the updates have been applied. A reference to the model instance is passed to
the callback method as a parameter.

58.3. Fetching a model

To fetch a model, one or more values must first be specified using addBean() or
addBeanProperty() before invoking the fetch() operation.
Let's work through an example - here we have an entity bean called Customer:

We also have a bean called CustomerAction, which is responsible for creating and editing
Customer instances. Since we're only interested in editing a customer right now, the
following code only shows the editCustomer() method:

In the client section of this example, we wish to make changes to an existing Customer
instance, so we need to use the editCustomer() method of CustomerAction
to first load the customer entity, after which we can access it via the public customer
field. Our model object must therefore be configured to fetch the CustomerAction.customer
property, and to invoke the editCustomer() method when the model is fetched. We start
by using the addBeanProperty() method to add a bean property to the model:

The first parameter of addBeanProperty() is the alias (in this case
customer), which is used to access the value via the getValue() method.
The addBeanProperty() and addBean() methods can be called multiple times
to bind multiple values to the model. An important thing to note is that the values may come from multiple
server-side beans, they aren't all required to come from the same bean.

We also specify the action that we wish to invoke (i.e. the editCustomer() method).
In this example we know the value of the customerId that we wish to edit, so we can
specify this value as an action method parameter:

Once we've specified the bean properties we wish to fetch and the action to invoke, we can then fetch the
model. We pass in a reference to the action object as the first parameter of the fetch()
method. Also, since this is an asynchronous request we need to provide a callback method to deal with the
response. The callback method is passed a reference to the model object as a parameter.

When the server receives a model fetch request, it first invokes the action (if one is specified) before
reading the requested property values and returning them to the client.

58.3.1. Fetching a bean value

Alternatively, if you don't wish to fetch a bean property but rather a bean itself
(such as a value created by a producer method) then the addBean() method is used instead.
Let's say we have a producer method that returns a qualified UserSettings value:

The first parameter is the local alias for the value, the second parameter is the fully qualified
class of the bean, and the third (and subsequent) parameter/s are optional bean qualifiers.

58.4. Modifying model values

Once a model has been fetched its values may be read using the getValue() method.
Continuing on with the previous example, we would retrieve the Customer object via
it's local alias (customer) like this:

var customer = model.getValue("customer");

We are then free to read or modify the properties of the value (or any of the other values within its
object graph).

58.5. Expanding a model

We can use the Model API's ability to expand a model to load uninitialized branches of the objects in
the model's object graph. To understand how this works exactly, let's flesh out our example a little
more by adding an Address entity class, and creating a one-to-many relationship
between Customer and Address.

As we can see, the @OneToMany annotation on the getAddresses()
method specifies a fetch attribute of LAZY, meaning that by
default the customer's addresses won't be loaded automatically when the customer is. When reading the
uninitializedaddresses property value from a newly-fetched
Customer object in JavaScript, a value of undefined will be returned.

getValue("customer").addresses == undefined; // returns true

We can expand the model by making a special request to initialize this uninitialized
property value. The expand() operation takes three parameters - the value containing
the property to be initialized, the name of the property and an optional callback method. The following
example shows us how the customer's addresses property can be initialized:

model.expand(model.getValue("customer"), "addresses");

The expand() operation makes an asynchronous request to the server, where the
property value is initialized and the value returned to the client. When the client receives the
response, it reads the initialized value and appends it to the model.

58.6. Applying Changes

Once you have finished making changes to the values in the model, you can apply them with the
applyUpdates() method. This method scans all of the objects in the model, compares
them with their original values and generates a delta which may contain one or more changesets to
send to the server. A changeset is simply a list of property value changes for a single object.

Like the fetch() command you can also specify an action to invoke when applying updates,
although the action is invoked after the model updates have been applied. In a
typical situation the invoked action would do things like commit a database transaction, end the current
conversation, etc.

Since the applyUpdates() method sends an asynchronous request like the
fetch() and expand() methods, we also need to specify a callback
function if we wish to do something when the operation completes.

The applyUpdates() method performs a refresh of the model, retrieving the latest
state of the objects contained in the model after all updates have been applied and the action method
(if specified) invoked.