Chapter 11. Remote and Offline JDO

11.1. Detach and Attach
11.1.1. Configuring detachability
11.1.2. Detachable behavior
11.1.3. Detach and Attach Callbacks
11.2. Remote Persistence Managers
11.2.1. Standalone Persistence Manager Server
11.2.2. HTTP Persistence Manager Server
11.2.3. Client Persistence Managers
11.2.4. Data Compression and Filtering
11.2.5. Remote Persistence Manager Deployment

The standard JDO runtime environment is local and online. It is local in that JDO components such as persistence managers and queries connect directly to the datastore and execute their actions in the same JVM as the code using them. It is online in that all changes to persistent objects must be made in the context of an active persistence manager. These two properties, combined with the fact that persistence managers cannot be serialized for storage or network transfer, make the standard JDO runtime difficult to incorporate into some enterprise and client/server program designs.

Kodo extends the standard JDO runtime to add remote and offline capabilities in the form of Remote Persistence Managers and Detach and Attach APIs. The following sections explain these capabilities in detail.

11.1. Detach and Attach

A common use case for an application running in a servlet or application server is to "detach" objects from all server resources, modify them, and then "attach" them again. For example, a servlet might store persistent data in a user session between a modification based on a series of web forms. Between each form request, the web container might decide to serialize the session, requiring that the stored persistent state be disassociated from any other resources. Similarly, a client/server or EJB application might transfer persistent objects to a client via serialization, allow the client to modify their state, and then have the client return the modified data in order to be saved. This is sometimes referred to as the "data transfer object" or "value object" pattern, and it allows fine-grained manipulation of data objects without incurring the overhead of multiple remote method invocations (which is one of the things that often makes entity bean-based solutions slow). Version 1 of the JDO specification does not provide direct support for this pattern or any offline modification, since persistent object updates can only take place when the instance is associated with a persistence manager that has a transaction running.

Kodo provides support for this pattern by introducing detach and attach APIs that allow a user to detach a persistent instance, modify the detached instance, and attach the instance back into a persistence manager (either the same one that detached the instance, or a new one). The changes will then be applied to the existing instance from the datastore. Very similar APIs are being considered for inclusion in the JDO 2.0 specification, so they may become standard in the future.

11.1.1. Configuring detachability

In order to be able to detach a persistent instance, the metadata for the class must declare that it is eligible for detachment by using the detachable extension. Changes to this extension require that the class be re-enhanced. This is because detachability requires that the enhancer add additional fields to the class to hold information about the persistent instance's object id and state. The simplest example of the detachable extension is:

public class DetachExample
    implements java.io.Serializable
{
    private String someField;
}



<?xml version="1.0"?>
<jdo>
  <package name="com.somecompany">
    <class name="DetachExample">
      <extension vendor-name="kodo" key="detachable" value="true"/>
    </class>
  </package>
</jdo>

As mentioned previously, when a class is declared to be detachable, the Kodo enhancer will add additional fields to the enhanced version of the class. One of these fields is of type java.lang.Object, and holds an object that refers to the class' state, so that version checking can be done when the object is re-attached. If the persistent class does not use application identity, then a second String field will be added that will be used to store the stringified object id of the instance that was detached.

It is possible to define one or both of these fields and declare them the metadata for your class, which will prevent the enhancer from adding the default fields to the class, and will keep your enhanced class serialization-compatible with the unenhanced version (in case the client tier only has the unenhanced class definition to work with). The detached-objectid-field and detached-state-field class-level metadata extensions declare these fields. These fields must not be managed by JDO (they must have their persistence-modifier attribute set to "none").

public class DetachExample
    implements java.io.Serializable
{
    private String someField;
    private String detachedObjectId;
    private Object detachedState;
}


<?xml version="1.0"?>
<jdo>
  <package name="com.somecompany">
    <class name="DetachExample">
      <extension vendor-name="kodo" key="detachable" value="true"/>
      <extension vendor-name="kodo" key="detached-objectid-field"
        value="detachedObjectId"/>
      <extension vendor-name="kodo" key="detached-state-field"
        value="detachedState"/>

      <!-- string fields are normally managed by default, so explicitly set -->
      <!-- this field to unmanaged; we don't need to worry about the        -->
      <!-- detachedState fields because fields of type java.lang.Object     -->
      <!-- are not managed by default -->
      <field name="detachedObjectId" persistence-modifier="none"/>
    </class>
  </package>
</jdo>

11.1.2. Detachable behavior

The KodoPersistenceManager exposes six methods to support detach and attach functionality:

Detaching persistent instances will create unmanaged copies of the instances. The copy mechanism is similar to serialization, with the exception that only fields in the current fetch groups are traversed. Fields that are not in the current fetch groups will be set to the Java default for their type, and will be ignored when the detached instances are later re-attached.

Example 11.1. Detaching a Single Instance

import kodo.runtime.*;

...

// CLIENT:
// requests an object with a given oid
DetachExample detached = (DetachExample) getFromServer (oid);

...

// SERVER:
// detaches object and returns to client
Object oid = processClientRequest ();
KodoPersistenceManager kpm = (KodoPersistenceManager) pm;
DetachExample example = (DetachExample) kpm.getObjectById (oid, false);
DetachExample detached = (DetachExample) kpm.detach (example);
sendToClient (detached);

...

// CLIENT:
// makes some modifications and sends back to server
detached.setSomeField ("bar");
sendToServer (detached);

...

// SERVER:
// re-attaches the instance and commit the changes
DetachExample modified = (DetachExample) processClientRequest ();
kpm.currentTransaction ().begin ();
kpm.attach (modified);
kpm.currentTransaction ().commit ();

If relation-type fields need to be detached, then they need to be declared as being in the default-fetch-group, or, if the capability is available, be declared in the custom fetch groups configuration for the KodoPersistenceManager performing the detachment.

Example 11.2. Using Custom Fetch Groups for Detach

public class DetachExample
    implements java.io.Serializable
{
    private String someField;
    private DetachExampleRelation someRelation;
}


<?xml version="1.0"?>
<jdo>
  <package name="com.somecompany">
    <class name="DetachExample">
      <extension vendor-name="kodo" key="detachable" value="true"/>
      <field name="someRelation">
        <extension vendor-name="kodo" key="fetch-group" value="mygroup"/>
      </field>
    </class>
    <class name="DetachExampleRelation">
      <extension vendor-name="kodo" key="detachable" value="true"/>
    </class>
  </package>
</jdo>


import kodo.runtime.*;

...

// CLIENT:
// requests an object with a given oid
DetachExample detached = (DetachExample) getFromServer (oid);

...

// SERVER:
// detaches object and returns to client
Object oid = processClientRequest ();
KodoPersistenceManager kpm = (KodoPersistenceManager) pm;
kpm.getFetchConfiguration ().addFetchGroup ("mygroup");
DetachExample example = (DetachExample) kpm.getObjectById (oid, false);
DetachExample detached = (DetachExample) kpm.detach (example);
sendToClient (detached);

...

// CLIENT:
// makes some modifications and sends back to server
detached.setSomeField ("bar");
detached.getRelation ().setSomeOtherField ("baz");
sendToServer (detached);

...

// SERVER:
// re-attaches the instance and commit the changes
DetachExample modified = (DetachExample) processClientRequest ();
kpm.currentTransaction ().begin ();
kpm.attach (modified); // will recursively attach relation too and apply mods
kpm.currentTransaction ().commit ();

When attaching an instance whose representation has changed in the datastore since detachment, a JDOOptimisticVerificationException will be thrown upon commit or flush, just as if a normal optimistic conflict was detected. When attaching an instance that represents an object that has been deleted since detaching, or when attaching a detached instance into a PersistenceManager that has an earlier version of the object, a JDOOptimisticVerificationException will be immediately thrown when attach is invoked. In these cases, the RollbackOnly flag will be set on the transaction.

When detaching an instance that has been modified in the current transaction (and thus made dirty), the current transaction will be flushed. This means that when subsequently re-attaching the detached instances, Kodo assumes that the transaction from which they were originally detached would have been committed; if it has been rolled back, then the re-attachment process will throw a JDOOptimisticVerificationException. To avoid this possibility, modifications should be made outside of a transaction (with NontransactionalWrite enabled) and then detached, which will mitigate the potential for a conflict when the objects are later re-attached.

11.1.3. Detach and Attach Callbacks

Persistent instances can be notified when they are being detached or attached by implementing the kodo.runtime.PreDetachCallback kodo.runtime.PostDetachCallback kodo.runtime.PreAttachCallback and kodo.runtime.PostAttachCallback interfaces. These interfaces are analogous to the notification mechanisms provided by the javax.jdo.InstanceCallbacks interface (see Section 4.4, “InstanceCallbacks”).

  • PreDetachCallback.jdoPreDetach is called on the managed persistent instance before it be to be detached.

  • PostDetachCallback.jdoPostDetach is called on the detached copy of the persistent instance after the entire detach process is complete. The method takes a single java.lang.Object argument, which is the managed instance that the detached copy is the clone of. It can be used to transfer transient or unmanaged state between the managed instance and the detached instance.

  • PreAttachCallback.jdoPreAttach is called on a detached instance before it is to be attached.

  • PostAttachCallback.jdoPostAttach is called on the managed instance after it has been attached. It is only invoked after the entire attach process is complete. The method takes a single java.lang.Object argument, which is the corresponding detached instance. It can be used to transfer transient or unmanaged state between the detached instance and the attached instance.