Wiki

Clone wiki

Okapi / Okapi_Java_Persistence_API

Introduction

This wiki describes basic concepts behind the Okapi Java Persistence API (OJPA, API).

OJPA provides an API for storing objects in various persistence formats like JSON, XML, YAML, binary streams, relational or object databases, and many others.

The serialized objects are deserizlized incrementally from the storage.

The API guarantees that references between the objects are preserved while serialization, and restored after deserialization.

OJPA is optimized for speed and memory usage.

In this description we use Okapi core classes to illustrate the concepts, but OJPA is not dependent on the core of Okapi framework, and can be used with any other framework or set of classes, providing them with a persistence layer.

API parts

The OJPA consists of common modules, that provide the common functionality, and Okapi-specific modules.

Common modules

  • net.sf.okapi.persistence

  • net.sf.okapi.persistence.beans

  • net.sf.okapi.persistence.json.jackson

Okapi-specific modules

  • net.sf.okapi.persistence.beans.v0

  • net.sf.okapi.persistence.beans.v1

  • net.sf.okapi.steps.xliffkit.common.persistence.sessions

Examples

  • net.sf.okapi.steps.xliffkit.writer.XLIFFKitWriterTest

  • net.sf.okapi.steps.xliffkit.reader.XLIFFKitReaderTest

The concepts

The conceptual parts of OJPA will be described below:

  • bean

  • session

  • reference

  • frame

  • anti-bean

  • proxy bean

  • version

Beans

A bean in general is a Java class with no-arguments constructor, private fields, and public getters and setters accessing the fields. Example of a bean:

public class TestBean {

    private int data;
    private boolean ready;

    public TestBean() { // No-arguments constructor
        super();
        data = -1;
        ready = false;
    }

    public int getData() { // Getter for the private data field
        return data;
    }

    public void setData(int data) { // Setter for the private data field
        this.data = data;
    }

    public boolean isReady() { // Getter for the private ready field
        return ready;
    }

    public void setReady(boolean ready) { // Setter for the private ready field
        this.ready = ready;
    }
}

Beans are in the core of OJPA, the bridge between a persistence framework and non-bean core classes. The only action required of a developer who wants to persist his/her classes with OJPA is to create OJPA-compliant beans and register them with OJPA.

The API provides mechanisms for creation of beans and registration of them within persistence sessions. Also the API controls versions of the beans, providing backwards compatibility with older beans.

External serialization/persistence frameworks like JSON Jackson or Hibernate access the beans, and the OJPA handles synchronization of the beans and their corresponding core classes.

The beans constitute an intermediate layer between core classes and persistence frameworks, imposing no constraints on the core classes design.

The OJPA classes access the beans by getters and setters, Java reflection is not used.

Why beans?

  • The beans are known to most of widely-used persistence frameworks.

  • The beans are supported by Java IDEs, which create getters and setters for the developer.

  • The beans are persistence-framework-independent, the same bean can be accessed by different frameworks.

  • The beans allow to add persistence to existing well-tested non-bean classes without the need to modify them to comply with a framework's expectations for method signatures etc.

  • The beans allow to add persistence to Java core classes, and to 3-rd party libraries which code is not likely to be modified.

  • The beans allow to control which parts of the core class should be persisted and assign proper names quickly.

  • The beans are faster than reflection-based alternatives.

Bean, step-by-step

All beans in OJPA are subclasses of the net.sf.okapi.persistence.PersistenceBean class.

PersistenceBean declares 3 abstract methods a bean-writer is expected to implement. Actually those methods bind the bean with the related core class.

Let's create a bean for the TestClass core class:

public class TestClass {

    private int data;
    public boolean ready;

    public TestClass(int data) {
        this.data = data;
        ready = true;
    }

    public int getData() {
        return data;
    }
}
  • In Eclipse choose New/Class. Type in the bean's name. It's a good practice to take the core class name and add the Bean suffix to it. For the TestClass the bean name would be TestClassBean:

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a1.png

  • To choose the superclass, press the Browse button next to the Superclass box, start typing "PersistenceBean", choose net.sf.okapi.persistence.PersistenceBean, hit Ok, then Finish.

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a2.png

Eclipse will create a new class:

package net.sf.okapi.persistence.wiki;

import net.sf.okapi.persistence.PersistenceBean;

public class TestClassBean extends PersistenceBean<PutCoreClassHere> {

}

Some text is underlined with a red wave: TestClassBean and PutCoreClassHere.

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a3.png

  • Select PutCoreClassHere and replace it with the name of the core class: TestClass.

  • Point your mouse at TestClassBean and choose Add unimplemented methods.

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a4.png

Eclipse will create stubs for the 3 abstract methods:

public class TestClassBean extends PersistenceBean<TestClass> {

    @Override
    protected TestClass createObject(IPersistenceSession session) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected void fromObject(TestClass obj, IPersistenceSession session) {
        // TODO Auto-generated method stub

    }

    @Override
    protected void setObject(TestClass obj, IPersistenceSession session) {
        // TODO Auto-generated method stub

    }
}
  • Add the fields to be persisted. You can copy them from the core class and give them different names if so needed. The fields should be private:
public class TestClassBean extends PersistenceBean<TestClass> {

    private int data;
    private boolean ready;

    @Override
    protected TestClass createObject(IPersistenceSession session) {
...
  • Add the following code to bind the bean with the core class:
public class TestClassBean extends PersistenceBean<TestClass> {

    private int data;
    private boolean ready;

    @Override
    protected TestClass createObject(IPersistenceSession session) {
        return new TestClass(data);
    }

    @Override
    protected void fromObject(TestClass obj, IPersistenceSession session) {
        data = obj.getData();
        ready = obj.ready;
    }

    @Override
    protected void setObject(TestClass obj, IPersistenceSession session) {
        obj.ready = ready;
    }
}
  • Right-click inside the class and choose Source/Generate Getters and Setters...:

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a5.png

  • Press Select All, then Ok.
public class TestClassBean extends PersistenceBean<TestClass> {

    private int data;
    private boolean ready;

    @Override
    protected TestClass createObject(IPersistenceSession session) {
        return new TestClass(data);
    }

    @Override
    protected void fromObject(TestClass obj, IPersistenceSession session) {
        data = obj.getData();
        ready = obj.ready;
    }

    @Override
    protected void setObject(TestClass obj, IPersistenceSession session) {
        obj.ready = ready;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public boolean isReady() {
        return ready;
    }

    public void setReady(boolean ready) {
        this.ready = ready;
    }
}

Complex beans

Very often core objects contain object fields. The developer is expected to provide beans for all internal classes as well as for the core classes that contain them.

An OJPA bean can contain ONLY the fields that are:

  • simple types (int, String, boolean, enum, ...)

  • other OJPA beans (subclasses of PersistenceBean)

  • container types (arrays, maps, lists, sets, ...) which elements are of a simple type or OJPA beans

A good example would be the AltTranslationBean bean:

public class AltTranslationBean extends PersistenceBean<AltTranslation> {
    private String srcLocId;
    private String trgLocId;
    private TextUnitBean tu = new TextUnitBean();
    private AltTranslationType type;
    private int score;
    private String origin;
... 

It handles persistence of the AltTranslation class:

public class AltTranslation implements Comparable<AltTranslation> { 
    LocaleId srcLocId;
    LocaleId trgLocId;
    TextUnit tu;
    AltTranslationType type;
    int score;
    String origin;
...

As you can see, origin, score, and the enum type are the same in the bean and the core class. But srcLocId and trgLocId are stored as String, and not LocaleId. The bean code handles conversion between LocaleId and String.

The TextUnit type requires a bean. It is important to instantiate beans after the declaration unless you want to write constructors for your beans.

    private TextUnitBean tu = new TextUnitBean();
    private List<TextPartBean> parts = new ArrayList<TextPartBean>();

Universal beans

The core classes might contain fields, which actual type is unknown at compile-type. For instance, IResource resource in the Event class. At run-time it's initialized with an actual object implementing IResource, and definitely we want to store all fields of that object.

OJPA defines the 3 "universal" beans in net.sf.okapi.persistence.beans:

  • TypeInfoBean - stores the class name of the actual object, and during deserialization tries to instantiate that object, presuming the constructor is no-arguments. This class is used when OJPA finds no registered bean for the actual object.

When there's no mapping registered for a core class, OJPA tries to find the closest superclass mapped with a bean, all the way up to the Java Object base class, which bean is TypeInfoBean.

  • FactoryBean - also stores the class name of the actual object, but then finds a bean class for the actual object, creates the bean, initiates it with the actual object, and stores the created bean in its content field, so the FactoryBean is a wrapper for the actual object's newly created bean.

In some cases FactoryBean doesn't store the object itself, but only a reference to it. See the References section for details.

  • ReferenceBean - also stores the class name of the actual object, but doesn't store the object itself, just a reference to that object. See the References section for details.

Sessions

The beans are written and read with sessions. Every persistence format requires a separate session that supports that given format.

Furthermore, every set of core classes requires a separate session derived from the base session that handles its persistence format.

The session takes care of versions, name spaces, and mappings of beans to core classes. A session can be in one of the 3 states: reading, writing, and idle.

There's a base session class net.sf.okapi.persistence.PersistenceSession, and all format-specific sessions should be derived from that class. For instance, JSON Jackson session (net.sf.okapi.persistence.json.jackson.JSONPersistenceSession) is a subclass of PersistenceSession. It employs persistence features of the Jackson framework to write and read a JSON stream. But the Okapi framework defines a session derived from the base net.sf.okapi.persistence.json.jackson.JSONPersistenceSession in net.sf.okapi.steps.xliffkit.common.persistence.sessions.OkapiJsonSession that handles beans for Okapi core classes.

PersistenceSession declares abstract methods a developer that wants to introduce a new persistence format, is expected to implement. Those methods are:

Method Method description Parameters Parameters description
startWriting initializes the session for writing OutputStream outStream the output stream to write to
writeBean writes a given bean to the output stream IPersistenceBean bean, String name the bean is labeled with the name (if the current format supports labels)
endWriting finishing writing OutputStream outStream the stream writing was performed to
startReading initializes the session for reading InputStream inStream the input stream to read from
readBean reads the next bean from the input stream Class beanClass, String name the expected class of the bean and expected label
endReading finishes reading InputStream inStream the stream reading was performed from
getMimeType returns the MIME type of the session format (if applicable)
convert converts a given object to the expected class Object obj, Class expectedClass the object to be converted to the expected class

Session, step-by-step

All sessions in OJPA are subclasses of the net.sf.okapi.persistence.PersistenceSession class.

If you want to implement a new persistence format for your classes, create a new session class. You would also require a new session class if you want to use a different persistence framework for the same persistence format. For instance, JSON persistence with Jackson and flexjson will require 2 separate session classes.

  • In Eclipse choose New/Class and type in a name for the new session class, say XMLPersistenceSession.

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a6.png

  • To choose the superclass, press the Browse button next to the Superclass box, start typing "PersistenceSession", choose net.sf.okapi.persistence.PersistenceSession, hit Ok, then Finish.

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a7.png

Eclipse will create a new class:

public class XMLPersistenceSession extends PersistenceSession {

    @Override
    protected void endReading(InputStream inStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected void endWriting(OutputStream outStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected Class<?> getDefItemClass() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected String getDefItemLabel() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected String getDefVersionId() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected <T extends IPersistenceBean<?>> T readBean(Class<T> beanClass,
            String name) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected void startReading(InputStream inStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected void startWriting(OutputStream outStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected void writeBean(IPersistenceBean<?> bean, String name) {
        // TODO Auto-generated method stub

    }

    @Override
    public <T extends IPersistenceBean<?>> T convert(Object obj,
            Class<T> expectedClass) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getMimeType() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void registerVersions() {
        // TODO Auto-generated method stub

    }
}

If you are planning to subclass the session later, remove some of the methods and make the class abstract. We will remove the following method stubs:

Method Description
getDefItemClass returns the core object class that will be persisted with the session
getDefItemLabel returns a prefix for the label given to written objects (item1, item2, ... by default)
getDefVersionId returns a default version Id (described below)
registerVersions callback method of the version control (described below)

We will end up with this code:

public abstract class XMLPersistenceSession extends PersistenceSession {

    @Override
    protected void endReading(InputStream inStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected void endWriting(OutputStream outStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected <T extends IPersistenceBean<?>> T readBean(Class<T> beanClass,
            String name) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected void startReading(InputStream inStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected void startWriting(OutputStream outStream) {
        // TODO Auto-generated method stub

    }

    @Override
    protected void writeBean(IPersistenceBean<?> bean, String name) {
        // TODO Auto-generated method stub

    }

    @Override
    public <T extends IPersistenceBean<?>> T convert(Object obj,
            Class<T> expectedClass) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getMimeType() {
        // TODO Auto-generated method stub
        return null;
    }
}

JSON example

Let's take a look at how the JSON persistence session is implemented.

net.sf.okapi.persistence.json.jackson.JSONPersistenceSession is an abstract class derived from net.sf.okapi.persistence.PersistenceSession.

It declares private fields that store references to Jackson framework objects like ObjectMapper, JsonFactory, JsonParser, JsonGenerator, and initializes those objects in the session constructor.

  • The convert method invokes the Jackson mapper.convertValue() to convert a given object to an expected type.

  • readBean reads an object with the means of a Jackson parser controlling the object label.

  • writeBean writes an object with Jackson.

  • getMimeType returns "application/json".

  • readFieldValue is a private helper.

  • startReading configures the Jackson framework for reading, and reads the header from the input JSON stream.

  • endReading finalizes the Jackson parsing task.

  • startWriting instantiates a Jackson generator, and creates a temporary stream for the JSON output body part.

  • endWriting finalizes the Jackson generator to flush the output buffers to the temporary body stream, and then copies the header and the body parts to the output JSON stream.

The implementation is that cumbersome, because the header, containing reference frames (described below) is built in the process of serialization.

The net.sf.okapi.steps.xliffkit.common.persistence.sessions.OkapiJsonSession class binds Okapi core classes with JSONPersistenceSession. Here we implement the methods we removed from the superclass.

  • registerVersions registers versions of Okapi-specific beans.

  • getDefItemClass returns the Okapi Event class as the default serialization class. The information about the item class is stored in the header, and doesn't prevent you from storing instances of different classes within the same session. You just specify the default class the session is handling. In Okapi the session stores events.

  • getDefItemLabel returns a prefix for the labels that OJPA will automatically assign to stored objects. If this method returns an empty string or null, the API uses the default "item" prefix.

  • getDefVersionId returns Id of the version that should be used in serialization. Deserialization controls versions based on the info read from the JSON header, or in whatever else way applicable for a given persistence format.

So, to summarize, we have the base class PersistenceSession for any persistence format, then we have its subclass JSONPersistenceSession that handles JSON Jackson format, and we have its next level subclass OkapiJsonSession that handles Okapi serialization to JSON.

An example of a parsed JSON file created with the OkapiJsonSession:

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a8.png

The same as raw JSON text:

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a9.png

Versions

The set of core classes might change with the time. New core classes can be added to the core framework, some classes deleted in the new versions. The developer might want to create new beans for those new classes, change or remove existing beans. All these situations would make old serialized beans unreadable.

OJPA provides the version control that allows to read older beans and instantiate new core classes from older beans, thus providing backwards compatibility. Also if you have several versions, you can choose with which of them you want to serialize your objects.

A version is a set of beans plus the version driver.

It's advisable to place all beans in a separate package, and not in the packages of core classes.

As a general rule, don't change the interface of the beans that have been released to the user. You should change their implementation if you change core classes, but if you need to add/remove getters, setters, change arguments, add new beans, or remove some of the beans, you should create a new version with the modified beans. Again, this rule applies only to released versions. Once the version is released, the set of beans, the sets of their public methods, and the method signatures should remain intact.

Though you are absolutely free to change insides of the beans. When you change your core classes, your beans will most likely start producing compile errors, because they were written for old core classes. Update the beans' code (normally just slight changes) in all version packages to reflect the change in core classes. If you do it, older versions are read correctly, and all beans are backwards-compatible.

Version, step-by-step

  • create a new package for the new version. In Okapi there are 2 packages: net.sf.okapi.persistence.beans.v0 for Version 0.0 (demo purposes), and net.sf.okapi.persistence.beans.v1 for Version 1.0 of JSON persistence.

  • create your bean classes in the new package. If you want to modify beans from previous versions, copy them to the new package.

  • create a version driver. The version driver is a class that defines a unique version Id, registers beans of the version, and does other things to provide backwards compatibility.

In Eclipse choose New/Class. Type in a good name for the version driver, for instance, "OkapiBeansVersion1".

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a10.png

  • press the Add button next to the Interfaces box, select the IVersionDriver:

http://okapi.googlecode.com/svn/trunk/okapi/steps/xliffkit/src/main/java/net/sf/okapi/persistence/wiki/a11.png

  • hit Ok, then Finish. Eclipse will create a version driver stub:
public class OkapiBeansVersion1 implements IVersionDriver {

    @Override
    public String getVersionId() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void registerBeans(BeanMapper beanMapper) {
        // TODO Auto-generated method stub

    }
}
  • add a public static Id for the version. It shouldn't be just "1.0", but something unique. It's a good idea to include in the version Id the framework name, like in "OKAPI 1.0":
public class OkapiBeansVersion1 implements IVersionDriver {

    public static final String VERSION = "OKAPI 1.0";

    @Override
    public String getVersionId() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void registerBeans(BeanMapper beanMapper) {
        // TODO Auto-generated method stub

    }
}
  • implement getVersionId. Simply
    @Override
    public String getVersionId() {
        return VERSION;
    }
  • register beans of this version, and beans of previous versions if used in the new version too:
    @Override
    public void registerBeans(BeanMapper beanMapper) {
        beanMapper.registerBean(Event.class, EventBean.class);      
        beanMapper.registerBean(TextUnit.class, TextUnitBean.class);
        beanMapper.registerBean(RawDocument.class, RawDocumentBean.class);
        beanMapper.registerBean(Property.class, PropertyBean.class);
    }

You register your beans by calling registerBean of the bean mapper passed to the method with its arguments. Actual parameters will be the core class and the bean class. If you have something like this,

        beanMapper.registerBean(Event.class, net.sf.okapi.persistence.beans.v1.EventBean.class);        
        beanMapper.registerBean(Event.class, net.sf.okapi.persistence.beans.v2.EventBean.class);        
        beanMapper.registerBean(Event.class, net.sf.okapi.persistence.beans.v3.EventBean.class);        

then net.sf.okapi.persistence.beans.v3.EventBean.class will be mapped to Event.class, and all previous mappings will be overridden. You can use it to register all beans of a previous version with a single call of its registerBeans, and then add your version-specific registerBean calls for the beans unique for the new version.

Every bean should be registered with the session that will use it. The session establishes an association between a core object and its bean. Registration is necessary for the API to be able to automatically store a given object with a right bean.

The API provides the BeanMapper class, which a session instantiates internally to handle bean mapping. The session calls of registerBean are delegated to the internal BeanMapper.

  • optionally (least likely for new versions), you can assign mapping of outdated version Id's and class names with the two helper static classes: VersionMapper and NamespaceMapper. The first one is used to map old version Id's to new ones. The second one is helpful to resolve changed class names:
        VersionMapper.mapVersionId("1.0", VERSION);
        NamespaceMapper.mapName("net.sf.okapi.steps.xliffkit.common.persistence.versioning.TestEvent", net.sf.okapi.persistence.beans.v0.TestEvent.class);

Version registration

The session is responsible for registration of all versions it will support. You won't need to write a new session class for a new version of beans. You just implement the abstract method PersistenceSession.registerVersions.

In net.sf.okapi.steps.xliffkit.common.persistence.sessions.OkapiJsonSession the version drivers for 2 versions of Okapi beans are called PersistenceMapper and OkapiBeans:

    @Override
    public void registerVersions() {
        VersionMapper.registerVersion(PersistenceMapper.class); // v0
        VersionMapper.registerVersion(OkapiBeans.class);    // v1
    }

References

OJPA provides persistence of references inside an object, and between objects. The reference persistence mechanism employs the following concepts:

  • reference Id

  • root object

  • internal reference

  • external reference

  • frame

  • anti-bean

  • proxy bean

First we'll define them, then explain how they do the job.

Reference Id

Every bean subclassed from PersistenceBean has a unique long refId field, and the reference fileds of FactoryBean and ReferenceBean use those refId's to link beans.

Root object

The root object is the object for which the method session.serialize is called.

In OkapiJsonSession the root object is an event. OJPA finds a bean for the class Event, and stores all its internal object fields (like IResource resource) as beans being part of the EventBean for the root object.

Internal reference

Internal references are references inside the root object (i.e. references between the root object's fields and their parts). Some object fields can contain references to the root object itself.

External reference

External references are references between a part of one root object and a part of another root object.

Frame

Frame is a set of root objects linked with references. The frames are deserialized as a whole for OJPA to be able to restore references between the objects it the frame.

Frames are stored by the session as a set of refId's of root objects. JSONPersistenceSession stores the information about frames in the header, and the beans-members of the frame along with other non-linked objects. You can see the frames in the screenshots in the JSON example session.

Frames, internal and external references are detected automatically by OJPA, there's no need for the developer to specify frames.

If an object contains only internal references, no frame is created for it. In other words, frames always contain 2 or more root objects.

Anti-bean

Anti-bean is a "virtual" bean representing a "physical" bean stored as part of another bean.

To put differently, an anti-bean is a reference to the bean, not the bean itself.

Anti-bean has a negative refId, which is the negated refId of the bean it references.

Anti-beans are created for root objects only, when the root object was included in another bean, which referenced it, and that bean has already been serialized. Not to store the already stored bean several times thus braking references, the anti-beans are introduced.

Proxy bean

Proxy beans are used while deserialization to resolve object references. OJPA creates one proxy bean for every registered bean class and keeps them in a map.

A proxy bean is asked to create a core object when there's an external reference to an object handled by another bean, and that other bean has not yet created its object.

The proxy bean knows how to instantiate that object's class, it creates the object, sets the reference to it, and puts the created referenced object in an object cache. When that second bean is ready to create its object and set its fields, it finds the already created object in the cache, and just sets the fields of the found object -- i.e. the one the first object has created and now refers to.

Factory bean vs Reference bean

When there's an object field, and you know that it always holds a reference to an object, created and "owned" by another object, then in the bean create a ReferenceBean field for that object field.

A good example for Okapi would be GenericSkeletonPart core class and GenericSkeletonPartBean.

public class GenericSkeletonPart {

    StringBuilder data;
    IResource parent = null;
    LocaleId locId = null;
public class GenericSkeletonPartBean extends PersistenceBean<GenericSkeletonPart> {

    private String data;
    private ReferenceBean parent = new ReferenceBean();
    private String locId;

The parent field of GenericSkeletonPart always contains a reference to a resource of an event. The event, not the skeleton part, instantiates that resource in the Event class and holds the reference to the created resource. The skeleton part just refers to that resource object.

Even though the FactoryBean class also handles references, there's a fundamental difference between the two classes.

There's no way for OJPA to detect, who especially created an object in an object field of a core class, and whether it should store the content of that object, or just a refId.

If a bean with a FactoryBean field finds during its serialization a cached bean for that internal object, it stores a reference (that bean's refId).

If it doesn't find it, it stores the object's content within the internal FactoryBean, and puts the FactoryBean into the bean cache.

On the other hand, if a bean with a ReferenceBean field doesn't find a cached bean for that internal object, it creates the bean, puts it into the bean cache, but doesn't store its content, but stores the reference to that bean, which will be hooked up later by its owner object's bean.

In practice, if we had declared the parent filed as FactoryBean instead of ReferenceBean, and the skeleton part were to be serialized first, it would have stored the huge resource part inside itself, and the event, that actually owns that resource, would have had to store just a reference to the bean in that skeleton part.

OJPA is smart enough to successfully restore references in both cases, and the deserialization results will be the same, but if a FactoryBean is used in our case, the JSON file will become hardly comprehensible, as events will be missing its integral parts, and instead having just references to their property as if owned by another bean. Something similar to finding tire prints of your dear car leading to your neighbor's garage.

What's next

Currently OJPA handles stream-based serialization formats like JSON, XML etc. The next step would be to handle random access to the stored objects.

Updated