XSnapshot User Guide

This document will describe various XSnapshot use cases within an application, as illustrated in the example application. We assume that you have already read the Quick Start to set up XSnapshot within your project's build process.

Declaring Basic Snapshots

You create snapshot objects by adding XSnapshot tags to your POJO data model. The @xsnapshot.snapshot-class tag at the class level directs XSnapshot to produce a snapshot object for a POJO, and the @xsnapshot.property tag tells XSnapshot what properties to include in a particular snapshot. You can declare multiple snapshots for a single POJO.

This is a simple example to show basic XSnapshot definitions. Note that this is similar to @ejb.value-object

 * @xsnapshot.snapshot name="simple" class="SimpleFoo"
 * @xsnapshot.snapshot name="full" class="FullFoo" 
public class Foo {
  // ... private fields

  /** @xsnapshot.property match="*" */
  public String getName() { ... }
  public void setName(String s) { ... } 

  /** @xsnapshot.property match="full" */
  public String getDescription() { ... }
  public void setDescription(String s) { ... } 

This example will generate two snapshot objects called SimpleFoo and FullFoo. The match="*" will direct XSnapshot to include the name property in all snapshots. The match="full" on getDescription will tell XSnapshot to only include description in the snapshot that matches the "full" snapshot, i.e., FullFoo.

Using XSnapshotUtils

The XSnapshotUtils object can be used to create snapshot object instances from POJOs and copy the values from snapshots back to the POJO model. This is the common usage pattern:

Foo foo = ...;

// convert to snapshot with specified name
// the name is the string argument that matches the name as specified in
// @xsnapshot.snapshot-class tag
SimpleFoo simple = xsnapshotUtils.createSnapshot(foo, "simple");
FullFoo full = xsnapshotUtils.createSnapshot(foo, "full");
// create model
Foo newFoo = (Foo)xsnapshotUtils.createModel(full);

// copy into existing model
xsnapshotUtils.copyIntoModel(full, foo);

// note that createModel is the equivalent of 
// copyIntoModel(full, obj = new Foo()), return obj;

Please see the quick start guide for info on how to set up the xsnapshotUtils object using Spring wiring. This object is not a singleton; it depends on an XSnapshot registry at runtime.

Handling Collections and Nested Components

XSnapshot is powerful because, unlike BeanUtils.copyProperties and @ejb.value-object, it can handle recursing into components nested within objects, converting deeper parts of the POJO object graph into snapshots. This is a variant of the value objects/data transfer objects (DTO) pattern, and is incredibly useful when you want to serialize pieces of POJO models over a remoting framework (RMI, Hessian, AJAX/DWR, SOAP, etc.)

Here's an example adapted from the sample application:

 * @xsnapshot.snapshot-class name="details" class="EmployeeDetailsSnapshot"
public class Employee {

   * @xsnapshot.property match="*"
  public String getLastName() { ... } 
   * @xsnapshot.property match="*" name="locationId" snapshot="simple"
  public Location getLocation() { ... }

   * @xsnapshot.array match="*" name="projectIds"
   *    model-element-type="Project"
   *    element-snapshot="simple"
  public Set getProjects() { ... }

Assuming Project and Location both have "simple" snapshots, the above example will generate a class called EmployeeDetailsSnapshot

public class EmployeeDetailsSnapshot {

  public String getLastName() { ... } 
  public SimpleLocation getLocation() { ... } 
  public SimpleProject[] getProjects() { ... } 

The resulting EmployeeDetailsSnapshot can be freely serialized, unlike the Employee object. This is because the Project and Location objects may be part of bidirectional relationships; if you try to serialize an Employee directly, you end up also grabbing all of the associated projects and locations, which in turn may grab every employee employee in the database!

Also note that the @xsnapshot.array tag converts a collection into an array, which can sometimes be more easily handled over certain remoting frameworks like Axis. Earlier versions of the JAX-RPC spec did not permit java.util collections to bind to XML types.

Handling Maps

... coming soon ...

Type Converters

... coming soon ...

Struts Integration

Defining ActionForms

ActionForms are really just a special kind of DTO/snapshot, and can be easily defined with XSnapshot doclet tags. This is an example taken from the sample application:

 * @xsnapshot.snapshot-class name="form"
 *   class="net.sf.xsnapshot.example.presentation.web.struts.forms.EmployeeForm"
 *   extends="org.apache.struts.validator.ValidatorActionForm"
public class Employee {

  // setters and other primitive properties omitted for brevity

  /** @xsnapshot.property match="*" */
  public String getLastName() { ... }

   * @xsnapshot.property match="form" 
   *   type="net.sf.xsnapshot.example.presentation.web.struts.forms.DateInputModel"
   *   init-value="new net.sf.xsnapshot.example.presentation.web.struts.forms.DateInputModel()"
   *   snapshot-transformer="to-date-input-model"
   *   model-transformer="from-date-input-model"
  public Date getDateOfBirth() { ... }
   * @xsnapshot.property match="form" 
   *   name="locationId" type="java.lang.Long" 
   *   nested-property="id" copy-to-model="true"
  public Location getLocation() { ... }

   * @xsnapshot.array match="form" name="projectIds"
   *    model-element-type="net.sf.xsnapshot.example.data.model.Project"
   *    snapshot-element-type="java.lang.Long" element-nested-property="id"
  public Set getProjects() { ... } 

The resulting generated form will look something like this:

public class EmployeeForm extends ValidatorActionForm {

  public String getLastName() { ... } 
  public DateInputModel getDateOfBirth() {  ... } 
  public Long getLocationId() { ... }
  public Long[] getProjectIds() { ... } 

There are several salient points that can be made from this example:

  • By declaring the generated snapshot object to extend ValidatorActionForm it can be used directly as a Struts form bean.
  • String properties can be incorporated directly in the snapshot.
  • Dates require special treatment. Here, we specify that the form bean property should be of type DateInputModel, a class with three String properties for year, month and day. This allows us to use three separate <html:select> widgets on the JSP. The property-level conversion is handled by custom type converters, which are registered with the XSnapshotRegistry via the Spring configuration.
  • Nested components (Location and Project) are handled specially, because they need to be "flattened" to numeric IDs for use with <html:select> option lists.
  • The "name" attribute defines what name the corresponding property will have on the snapshot object, if you want it to be something different than the same as the model.
  • Similarly, the "type" and corresponding "snapshot-element-type" attributes specify the type of the property in the snapshot that corresponds to the model property.
  • The "model-element-type" attribute is necessary to tell XSnapshot what type of objects should be within a collection, since it cannot be determined statically at build time.
  • The "nested-property" (and "element-nested-property" when the POJO entities are within a collection) tells XSnapshot that when building the snapshot, it should flatten the contained object to the value of its specified property. So, in this case, a Location object is reduced to whatever location.getId() returns.
  • The "copy-to-model" is a special tag that indicates whether XSnapshot should attempt to copy the value for this property from the snapshot back to the model. This particular case of converting an ID back to an entity must be explicitly specified.

Converting ActionForms to POJOs in your Actions

There are two basic patterns to using XSnapshot in basic CRUD Struts actions. First, populating a form for editing:

Employee emp = employeeBean.getEmployee(id);
EmployeeForm form = xsnapshotUtils.createSnapshot(emp);

request.setAttribute("EmployeeForm", form);

... and next, save a new object or updating an existing object from a form:

EmployeeForm empForm = (EmployeeForm)form;
Employee emp = null;
if (empForm.getId() == null) { 
  emp = new Employee();
} else {
  emp = employeeBean.getEmployee(empForm.getId());

That's it! All of the complexity is in the POJO model and in the Spring configuration, where it belongs.

The really exciting thing about this is, whenever you add a new property to your model, you don't have to touch your action. Just your JSP. XSnapshot even will convert IDs from forms back to the related entities with callbacks to persistence. BeanUtils can't do that.

Handling Nested Components

XSnapshot needs to be configured with persistence callbacks to convert IDs in forms back to the related objects behind the scenes, to keep Actions lean and mean as above. This is described partially in the quick start guide. All you have to do to make the above examples work is make sure that there is a transformer registered with XSnapshot under the key "inverse-nested-property". Whenever an XSnapshot mapping specifies a property with nested-property="id" (or equivalent in collection/map forms), this transformer is implicitly called as part of the copyIntoModel process to convert the property back into the full value.

By imbuing this transformer with access to persistence, it is trivial to "convert" an ID to an entity: it's just a load. Effectively, this transformer is like a special DAO, which can be configured in Spring like all your other DAOs. Currently, XSnapshot provides an id-to-entity converter for both Hibernate 2.x and 3.x.