The Persistence System of the VizIR Framework

Roman Divotkey

November 19, 2003

Abstract

This documentation gives an introduction to the persistence system of the VizIR framework. It describes the purpose of the entities that can be persisted and their relation among each other. It contains examples of how to create, store, edit and delete these entities and how to register new types of feature descriptors to the framework. It also gives insights to the implemenation of the persistence system including some details about the use of the subsystem Hibernate and how to configure it.


Table of Contents

Purpose of the Persistence System
The Entities
Entities related to feature descriptors
Entities related to media objects
The Persistence System
The Persistence Manager
Transactions
Examples
The Bridge-Pattern used to hide the underlying Persistence System
The concrete implementation of the PersistenceSystem with Hibernate
What is Hibernate?
How to Configure Hibernate
Implementation of entities with Hibernate
Bibliography

Purpose of the Persistence System

The purpose of the persistence system is to store entities like media objects or feature descriptors permanently beyond the lifecycle of a java virtual machine and its objects held in memory. It provides mechanisms to retrieve the stored objects in an ordered way and gives the ability to manipulate and delete the persistent entities. The persistence system uses an interchangeable persistence framework as subsystem to implement this functionality. This makes the VizIR framework independent from the continuity and availability of the used persistence framework. In most circumstances the entities will be stored in an ordinary relational database that probably knows nothing about the object oriented structures we have in Java and especially in the VizIR framework. The persistence system builds a bridge between these two worlds and lets the developer use the persistent entities almost like any other objects in Java.

The Entities

A persistence system is nothing without the entities it can persist. The following part is divided into two sections. The first part describes the persistable entities concerning feature descriptors and the second part describes the persistable entities related to media objects. Although these two groups of entities are related, each of them represents a separate and more or less independent group and it might be a good idea to look at them separately.

Entities related to feature descriptors

The overview diagram shows the relationship between all the entities related to feature descriptors. The following sections will describe each entity in detail.

The relations between the entities related to feature descriptors.

DescriptorInfo

UML-Diagram of the DescriptorInfo interface.

The main purpose of this entity is to store information about a certain type of feature descriptor. Actually a DescriptorInfo entity represents the registration of a new feature descriptor at the VizIR framework. It holds all necessary information to view and describe the installed descriptors as well as the classname of the descriptor logic necessary to generate the descriptor data or calculate the distance between two descriptors (see also DescriptorLogic).

DescriptorInfoCollection

UML-Diagram of the DescriptorInfoCollection interface.

This entity represents a collection of descriptors registered in the VizIR framework. Beside the common attributes (name, description etc.) or rather its accessor methods, it provides methods to add and remove DescriptorInfo objects. The entries of the collection can be accessed through the well known Iterator interface.

DescriptorContainer

UML-Diagram of the DescriptorContainer interface.

The main purpose of DescriptorContainer is to provide a way to persist the Descriptor entity which cannot be persisted directly (see also Descriptor). It also maintains the relation to the corresponding DescriptorInfo entity. This relationship is necessary, because the descriptor data are stored at the media objects and the information about the descriptor must still be accessible.

DescriptorLogic

UML-Diagram of the DescriptorLogic interface.

The DescriptorLogic contains the algorithms to generate descriptor data of a certain media object. In most cases the created Descriptor objects will be private inner clases of the implementation of the DescriptorLogic. In addition to this it can calculate the distance between two descriptors of the same type.

This entity cannot be persisted directly by the persistence system. But because the algorithm itself is only static and does not contain any variable data, this is not really a restriction. A DescriptorLogic is only accessable by the corresponding DescriptorInfo entity, which contains its fully qualified class name (see also Descriptor).

Descriptor

UML-Diagram of the Descriptor interface.

This entity represents the feature descriptor related to a certain media object or, to say it a little bit shorter, the data of a feature descriptor (e.g the single values of a color histogram extracted from a still picture). Because the concrete implementations of this entity may vary a lot, the interface itself contains no methods or properties. In most cases it will be a private inner class of the corresponding DescriptorLogic which actually contains the algorithm to produce this data. The DescriptorLogic will also know how to access the data inside its Descriptor, so the missing accessor methods for data are not a problem (see also DescriptorLogic).

This entity cannot be persisted directly by the persistence system. It must be wrapped inside a DescriptorContainer which then will store the Descriptor using the Java serialization mechanism. This limitation is necessary to make it easy to add new types of descriptors to the framework and at the same time maintain the independency to the unterlying persistence system (see also DescriptorContainer).

Entities related to media objects

The overview diagram shows the relationship between the media entities. The following sections will describe each entity in detail.

The relations between the entities related to media objects.

Media

UML-Diagram of the Media interface.

This entity represents a media object inside the VizIR framework. It holds information about the media itself, like name and description and provides access to its media content.

MediaCollection

UML-Diagram of the MediaCollection interface.

This entity represents a collection of media objects stored within the VizIR framework. Beside the common attributes (name, description etc.) or rather its accessor methods, it provides methods to add and remove Media objects. The entries of the collection can be accessed through the well known Iterator interface.

The Persistence System

Now that all the entities have been introduced, we will have a closer look on how to make these entities persistent. This task is up to the Vizir Persistence System which will be described in detail by the following sections. In addition to this several examples will complete this documentation of the “exterior view” of the persistence system.

The term “persistence system” is used for at least two things. The first is the overall system - consisting of classes, interfaces and mappings - having the assignment of bringing the entities from the main memory to a more lasting persistent state inside an underlying database. The second thing is one particular class PersistenceSystem which is the starting point of all persistence operations.

UML-Diagram of the PersistenceSystem class.

All methods of PersistenceSystem are static and therefore can be accessed from everywhere without having a “global” instance, and without passing a parameter to each method that might have to persist something. The createXXX methods are factory methods used to create new instances of entities. It is necessary to have these factory methods, because all the entities are defined as interfaces and the implementing classes are hidden, so the new operator cannot be used.

The most important factory method is the createPersistenceManager method. It returns a new instance of a PersistenceManager which represents a session within one can store and retrieve entities from the database.

The PersistenceSystem is initialized by the method initialize. A call to this method will configure and activate the underlying persistence system. How the underlying system is configured depends on the concrete implementation.

Note: this method should be called only once - a second call will result in a runtime exception. When the PersistenceSystem is not needed any more, a call to close will release all used resources and perform a clean shutdown.

The Persistence Manager

An instance of the PersistenceManager represents a single session under which entities can be persisted or retrieved. A session in general is bounded to a certain database connection. However the database connection is hidden by the persistence system and can not be addressed directly.

UML-Diagram of the PersistenceManager interface.

The PersistenceManager provides all methods necessary to persist, modify, delete and query entities. But it is important to remember, that only entities retrieved or created inside this session can be modified. Also it is definitely ok to use entities beyond the lifetime of a PersistenceManager it is necessary to reload those entities inside a newly created PersistenceManager in order to modify the persistent state. All modifications will take affect immediately inside the current session, however it depends on the configuration of the underlying persistence system, when these modifications will become visible to other sessions.

The methods of the PersistenceManager in detail:

  • store: This method takes an instance of an entity and makes it persistent. After a call to this method, any modifications to that entity are automatically persisted when the current transcation is commited, or when the PersistenceManager is closed.

  • delete: This method takes an instance of an entity as argument and removes the persisted state from the database. After a call to this method the specified entity is transient. Note: the specified entity must have been loaded within this session in order to use it with this method.

  • retrieveAll: This method takes a class of an entity as parameter. The result is a collection containing all entities of the specified class. A typical example for using this method is to retrieve all MediaCollections.

  • reload: This method can be used to make an entity from an earlier session visible to the current session. It takes an instance of an entity as parameter and returns the current persistent state of this entity.

  • currentTransaction: This method returns the current transaction of this session (see also Transactions).

  • close: A call to this method releases all resources used by this PersistenceManager. After a PersistenceManager has been closed is should not be used anymore.

  • isClosed: Returns true if this instance the PersistenceManager has been closed.

Transactions

The interface Transaction is responsible for mapping logical transaction to database transactions. How and if transactions are handled depends on the configuration of the underlying persistence system and the database itself. However if the standard idiom for tranactions is used, everything should work as expected no matter if transactions are available or not (see also Standard idiom for transactions).

UML-Diagram of the Transaction interface.

A newly created transaction returned by the PersistenceManager is not active and one should call begin before any persistence operation is performed. A call to commit will write the changes to the database and confirm the operations. A call to rollback however should only be performed if something went wrong. It is especially not suitable to use rollback to implement a Undo-function to take back some modifications.

Figure 1. Standard idiom for transactions

PersistenceManager pm = PersistenceSystem.createTransactionManager();
Transaction tx = null;

try {
  tx = pm.currentTransaction();
 
  // ... do some work here ...
   
  tx.commit();
}
catch (PersistenceException ex) {
  if (tx != null) {
    tx.rollback();
  }
  throw ex;
}
finally {
  pm.close();
}

Examples

This section will illustrate the use of the VizIR Persistence System by some basic but essential examples. All examples use transactions to persist the modifications, because using transactions is a good way to produce non breakable code. If transactions are not required at the moment, the persistence system can be configured not to use them. If the things change and transactions are needed they can be activated by adapting the configuration or to enable transaction handling at the underlying database without changing one line of code.

How to create a new DescriptorInfoCollection

This sample code demonstrates how to create a new DescriptorInfoCollection and to make it persistent.

Example 1. Creating and persisting a DescriptorInfoCollection

DescriptorInfoCollection descInfoCol;
descInfoCol = PersistenceSystem.createDescriptorInfoCollection();

descInfoCol.setName("MPEG-7 Visual Motion Descriptors");
descInfoCol.setDescription("The motion features of a video sequence "
                           + "provide the easiest access to its temporal "
                           + "dimension, and are hence of key "
                           + "significance in video indexing.");

PersistenceManager pm = PersistenceSystem.createPersistenceManager();
Transaction tx = null;

try {
  tx = pm.currentTransaction();
  tx.begin();
  pm.store(descInfoCol);
  tx.commit();
}
catch (PersistenceException ex) {
  if (tx != null) {
    tx.rollback();
  }
  throw ex;  
}
finally {
  pm.close();
}

How to create a new DescriptorInfo object

Now that we have a DescriptorInfoCollection, which is stored in the variable descInfoCol, we want to add a new DescriptorInfo object. This is actually a registration of a new type of descriptor at the VizIR framework.

Note that we have to reload the collection, because it has not been created or retrieved within this session.

Example 2. Creating a DescriptorInfo object

DescriptorInfo descInfo = PersistenceSystem.createDescriptorInfo();

descInfo.setName("Motion Activity");
descInfo.setLogicClassName("org.vizir.descriptors.MotionActivityLogic");
descInfo.setDescription("This descriptor captures this intuitive"
                        + "notion of 'intensity of action' or 'pace"
                        + "of action' in a video segment.");
                        
PersistenceManager pm = PersistenceSystem.createPersistenceManager();
Transaction tx = null;

try {
  tx = pm.currentTransaction();
  tx.begin();

  pm.store(descInfo);

  descInfoCol = (DescriptorInfoCollection) pm.reload(descInfoCol);
  descInfoCol.add(descInfo);
  
  tx.commit();
}
catch (PersistenceException ex) {
  if (tx != null) {
    tx.rollback();    
  }
  throw ex;  
}
finally {
  pm.close();
}                       

How to retrieve the entities of a collection

This example demonstrates how to query the entities within a collections. It simply writes the name of all elements contained by a collection to standard output stream. In this case we do not need any transactions, because no entity is modified and we do not have to persist a new state. Again we asume that the collection we want to query is specified by a variable descInfoCol created within a different session.

Example 3. Querying entities of a collection

PersistenceManager pm = PersistenceSystem.createPersistenceManager();

try {
  descInfoCol = (DescriptorInfo) pm.reload();
  for (Iterator it = descInfoCol.iterator(); it.hasNext(); ) {
    DescriptorInfo item = (DescriptorInfo) it.next();
    System.out.println("Descriptor: " + item.getName());
  }
}
catch (PersistenceException ex) {
  throw ex;
}
finally {
  pm.close();
}

How to modify a persisted entity

There is nothing special to do in order to modify a persisted entity. It just needs to be loaded into the current session and changed. When the current transaction is commited, the new state is persisted. In the example we change the DescriptorLogic classname of a DescriptorInfo object. The entity has been loaded in the variable descInfo in a previous session.

Example 4. Modifying a persisted entity

PersistenceManager pm = PersistenceSystem.createPersistenceManager();
Transaction tx = null;

try {
  tx = pm.currentTransaction();
  tx.begin();

  descInfo = (DescriptorInfo) pm.reload();
  descInfo.setLogicClassName("org.vizir.descriptors.NewDescriptor");
  tx.commit();
}
catch (PersistenceException ex) {
  if (tx != null) {
    tx.rollback();
  }
  throw ex;
}
finally {
  pm.close();
}

How to search for an entity by a property

This example shows how to query a certain entity, which we do not have from a previous session. Because in this version of the persistence system we do not have access to a query language, we must search the entities programatically. In this example we try to find all DescriptorCollections that start with the term MPEG in its name.

Example 5. Searching for entities by property

PersistenceManager pm = PersistenceSystem.createPersistenceManager();

try {
  Collection c = pm.retrieveAll(DescriptorInfoCollection.class);
  for (Iterator it = c.iterator(); it.hasNext(); ) {
    DescriptorInfoCollection descInfoCol = (DescriptorInfoCollection) it.next();
    if (descInfoCol.getName().startsWith("MPEG")) {
      System.out.println("Found: " + descInfoCol.getName());
    }
  }
}
catch (PersistenceException ex) {
  throw ex;  
}
finally {
  pm.close();
}

How to delete entities

Now that we can create entities and add them to collection, we also want to be able to delete them. This is achieved by a call to the PersistenceManager method delete. If we want to proceed very correctly we can remove the entity from all collections before deletion. But this is not really necessary because the persistence system will take care of the referential integrity.

In the following example we assume that we want to delete the DescirptorInfo object descInfo which is contained by the collection descInfoCol.

Example 6. Deleting an entity

PersistenceManager pm = PersistenceSystem.createPersistenceManager();
Transationc tx = null;

try {
  tx = pm.currentTransaction();
  tx.begin();

  descInfo = (DescriptorInfo) pm.reload();
  
  // The following two lines are not really necessary.
  descInfoCol = (DescriptorInfoCollection) pm.reload();
  descInfoCol.remove(descInfo);
  
  pm.delete(descInfo);
  
  tx.commit();
}
catch (PersistenceException ex) {
  if (tx != null) {
    tx.rollback();
  }
  throw ex;  
}
finally {
  pm.close();
}

How to build and register a new feature descriptor

A feature descriptor is defined by two classes. The first class is called DescriptorLogic which contains the algorithm to extract a feature from media. The second is the class Descriptor which contains the data of a feature extracted by the DescriptorLogic. Because the Descriptor class is only useful by its DescriptorLogic it is a good idea to make it a private static inner class of the DescriptorLogic. But this is only a recommendation, it is by no means necessary for persistence or any other aspect of the VizIR framework.

The example shows the basic structure of a new feature descriptor and how to register it. Note: instead of a byte array like in the example any data structure that is convenient can be used to store the feature data.

Example 7. Basic structure of a new feature descriptor

package org.vizir.descriptors;

import org.vizir.DescriptorLogic;
import org.vizir.Descriptor;
import org.vizir.Media;

public class MyDescriptorLogic implements DescriptorLogic {

  public DummyDescriptorLogic() {
  }

  public Descriptor extractDescriptor(Media m) {
    byte[] data;
   
    
    // ... extract the descriptor data here ...
    data = ...
    
    return new MyDescriptor(data);   
  }

  public float calculateDistance(Descriptor d1, Descriptor d2) {
    // .. calculate the distance of two feature descriptors.
    double distance = ...
    
    return distance;
  }

  private static class MyDescriptor implements Descriptor {
    private byte[] data;
    
    private DummyDescriptor(byte[] data) {
      this.data = data;
    }
  }
}

Now that we have a new DescriptorLogic class, we want to register it and add it to the collection descInfoCol

Example 8. Register the new feature descriptor

DescriptorInfo descInfo = PersistenceSystem.createDescriptorInfo();

descInfo.setName("My very own descriptor");
descInfo.setLogicClassName("org.vizir.descriptors.MyDescriptorLogic");
descInfo.setDescription("This is an example descriptor.");
                        
PersistenceManager pm = PersistenceSystem.createPersistenceManager();
Transaction tx = null;

try {
  tx = pm.currentTransaction();
  tx.begin();

  pm.store(descInfo);

  descInfoCol = (DescriptorInfoCollection) pm.reload(descInfoCol);
  descInfoCol.add(descInfo);
  
  tx.commit();
}
catch (PersistenceException ex) {
  if (tx != null) {
    tx.rollback();    
  }
  throw ex;  
}
finally {
  pm.close();
}                       

The Bridge-Pattern used to hide the underlying Persistence System

The concrete implementation of the entities differs depending on the underlying persistence system. And not only the entities, the PersistenceManager and its transactions are also heavily coupled with the persistence system. To decouple all these components from the concrete persistence framework like Hibernate or Castor the following design principle is used, which is similar to the Bridge-Pattern described in [Gamma95].

UML-Diagram of the design pattern used to hide the real persistence system .

An entity is represented by an interface having all necessary accessor methods for its properties. Using accessor methods instead of direct field access gives us the opportunity to do some additional work when the properties of an entity are accessed or modified. The concrete implementation of the entity can have additional properties which might be necessary for persistence. These additional properties are hidden and therefore do not disfigure the entity interface. Although this is a nice feature of this pattern, the main purpose of hiding implementation specific properties and functions is to be independent from the underlying persistence system, so that it can be easily replaced or fixed.

As mentioned before, the same design pattern is used for other components of the persistence system, like the PersistenceManager or Transaction. The factory methods of the PersistenceSystem use an implementation of the interface PersistenceFactory which is responsible for creating concrete instances of entities and components. Each implementation of the persistence system has to provide this factory, so that in order to change the underlying persistence system, the only thing to do is to use a different PersistenceFactory class. However, this new PersistenceFactory class may have to be configured in a different way, according to the persistence framework used.

A detailed view of the PersistenceSystem and the mechanism to hide the real implementation.

The concrete implementation of the PersistenceSystem with Hibernate

The following section describes how the persistence system is implemented by using the Hibernate persistence framework. Furthermore a brief introduction to Hibernate and its configuration is given. It should also be mentioned that Hibernate is an open source project and is published under the LGPL license.

What is Hibernate?

Excerpt from the Hibernate documentation:

Hibernate is a powerful, ultra-high performance object/relational persistence and query service for Java. Hibernate lets you develop persistent objects following common Java idiom - including association, inheritance, polymorphism, composition and the Java collections framework. Extremely fine-grained, richly typed object models are possible. The Hibernate Query Language, designed as a “minimal” object-oriented extension to SQL, provides an elegant bridge between the object and relational worlds. Hibernate is now the most popular ORM solution for Java.

The main thing to know is, that Hibernate maps the persistent state of java objects to a relational database and also provides a elaborate query mechanism to retrieve the objects. It maintains the relationships among the objects and encapsulates the relational database management systems so that nobody has to worry about the different SQL dialects.

For the moment it supports the following relational database management systems: Oracle, DB2, MySQL, PostgreSQL, Sybase, SAP DB, HypersonicSQL, Microsoft SQL Server, Informix, FrontBase, Ingres, Progress, Mckoi SQL, Pointbase and Interbase

How to Configure Hibernate

Hibernate needs some configuration in order to work properly. Especially the URL for the database and the login information have to be provided somehow. There are several ways to do this, but the most suitable way is to use a global configuration file for Hibernate. Other ways of configuring are described in the documentation of Hibernate.

A sample configuration file is included in the Hibernate package. At least the following things have to be adapted to the actual environment:

  • The actual database management system used

  • The jdbc driver

  • The connection URL to the database

  • The database user and password

A lot of different example configurations are provided within the configuration file which are commented out with a “#” at the beginning of the line. A valid configuration for a MySQL database could look like this:

Example 9. Example configuration for a MySQL database

## MySQL

hibernate.dialect net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class com.mysql.jdbc.Driver
hibernate.connection.url jdbc:mysql://192.168.123.180/vizir
hibernate.connection.username vizir
hibernate.connection.password joshua

The rest of the provided configuration file can stay untouched except anything special is required. The file should be named hibernate.properties and its location must be included in the classpath.

Implementation of entities with Hibernate

Hibernate does not need any bytecode processing, build-time code generation or inheritance from a persistence base class to accomplish persistence. But it has some requirements to persistable entities. This section will show the requirements of entities to make them persistable by Hibernate.

  • Accessor methods for persistent fields: Accessor methods follow the JavaBeans style properties and will be recognized by Hibernate automatically.

  • No-Argument default constructor: All persistent classes must have a default constructor (which may be non-public), so Hibernate can instantiate them using Constructor.newInstance().

  • Identifier property: Although having an identifier property is optional, it is a very good and common design decision. Using the Bridge-Pattern the identifier can be hidden from the user api anyway. The identifier property will be used for the primary key column of the database table. It can be of any primitive type or primitive “wrapper” type, java.lang.String or java.util.Date. For the VizIR framework the primitive wrapper type Long is used as identifier property because the primitive type long caused some problems with the current release of Hibernate.

    For further information about identifier (e.g. composite keys) please consult the Hibernate documantation.

Basic entities and relations

Following the design pattern described in the previous section, the Hibernate implementation of the entity DescriptorInfo would look like this:

A concrete implementation of the DescriptorInfo entity.

The implementation has the fields (name, description, logicClassName) necessary to store the data of the entity. It also contains the identifier (id) mentioned before. This identifier is not part of the DescriptorInfo interface and therefore cannot be accessed from “outside”, it is hidden. As for any persistent field Hibernate needs accessor methods for the identifier.

Hibernate needs additional information to store the persitent values in a database. It needs information how to store these entity, which table to use, which field to store in which column, how this entity is related to other entities. This information (also called mapping) can be provided in several ways. For the VizIR framework we use mapping files. These mapping files are plain XML files. The mapping for the DescriptorInfo entity can be seen in Figure 2. For more details about mappings and mapping files have a look to the original Hibernate documentation.

Figure 2. The XML mapping file for the DescriptorInfo entity.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
  <class name="org.vizir.persistence.hibernate.DescriptorInfoImpl" table="DescriptorInfo">
    <id name="id" type="long">
      <generator class="vm"/>
    </id>

    <property name="name" type="string"/>
    <property name="description" type="string"/>
    <property name="logicClassName" type="string"/>

  </class>
</hibernate-mapping>

The DescriptorInfo entities can usually be found within DescriptorInfoCollection entities. So let's have a look at this entity and how this relation is managed by Hibernate.

A concrete implementation of DescriptorInfo and DescriptorInfoCollection entity.

Note the additional setDescriptors and getDescriptors accessor methods. These are needed for Hibernate to maintain the relation. These methods are not part of the DescriptorInfoCollection interface and therefore hidden to the outside world. The provided interface of add and remove is more restrictive then the full access to the collection which is needed by Hibernate. Providing only these two methods to add and remove entities gives us full control about all manipulations and the chance to do some additional tasks when an entity is added or removed. This would not be possible if somebody had direct access to the collection holding the entities. This is the reason why it is important to hide the setDescriptors and getDescriptors accessors from the users of DescriptorInfoCollection. The same considerations are true for all other entities and their relations.

The mapping file for this class (Figure 3) contains information about the many to many relation of DescriptorInfo and DescriptorInfoCollection entities.

Figure 3. The XML mapping file for the DescriptorInfoCollection entity.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
  <class name="org.vizir.persistence.hibernate.DescriptorInfoCollectionImpl" table="DescriptorInfoCollection" >
    <id name="id" type="long">
      <generator class="vm"/>
    </id>

    <property name="name" type="string"/>
    <property name="description" type="string"/>

    <set name="descriptorInfos" cascade="none" table="DescInfo_to_DescInfoColl" lazy="true">
	  <key column="collection_id"/>
	  <many-to-many column="descriptorInfo_id" class="org.vizir.persistence.hibernate.DescriptorInfoImpl"/>
    </set>
  </class>
</hibernate-mapping>

Referential integrity

Referential integrity has to be maintained under all circumstances especially when deleting collections or entities stored in more than one collection. This can be achieved by using the Hibernate persistent lifecycle methods. These are callback methods that give us the chance to do some initialization or cleanup after save or load and before deletion or update.

Entities which use this mechanism have to implement the interface Lifecycle. For the VizIR persistence system only the onDelete is of interest. Here we can maintain the referential integrity of our data. Two main cases are important:

  1. Deletion of an entity: The entity must be removed from all collections which contain this entity.

  2. Deletion of a collection: Entities which are only stored inside this collection must also be deleted. All other entities are just removed from this collection.

In both cases we need the additional information which collections contain a certain entity. Although this information could be queried from the database using the Hibernate query language, it is much more convenient to have this information provided automatically by Hibernate. To achieve this we introduce an inverse mapping for the relationship of entities and their collections. This does not introduce new tables or columns in the database structure, it just gives us the possibility to ask an entity about its collections. Here is the new UML diagram:

A concrete implementation of DescriptorInfo and DescriptorInfoCollection entity with a bidirectional relation.

Note the new accessor methods getCollections and setCollections added to the DescriptorInfo class, which are needed by Hibernate to maintain the reverse mapping. The implementation of the Lifecycle interface is not shown in the diagram.

We have to take care of the integrity of this bidirectional relationship when adding or removing an entity. This means when adding an entity to a collection we put the entity into the internal set of the collection and vice versa the collection to the internal set of collections of the entity. This can all be done in the add and remove method of the collection. In Figure 4 the code for the add and remove methods of DescriptorInfoClass is shown.

Figure 4. Maintaining the bidirectional relationship among entities and collections

  /**
   * Adds a descriptorInfo object to this collection.
   *
   * @param info  the descriptorInfo object to be added to this collection
   */
  public void add(DescriptorInfo info) {
    descriptorInfos.add(info);
    ((DescriptorInfoImpl) info).getCollections().add(this);
  }

  /**
   * Removes a descriptorInfo object from this collection.
   *
   * @param info  the descriptorInfo object to be removed from this collection
   */
  public void remove(DescriptorInfo info) {
    descriptorInfos.remove(info);
    ((DescriptorInfoImpl) info).getCollections().remove(this);
  }

Gaining flexibility for new types of feature descriptors

To avoid the necessity of creating new mapping files and classes that follow the described design pattern for each new type of feature descriptor, the concept of descriptor container has been introduced. Descriptor container wrap the classes that hold the actual data of a feature descriptor. The container does not need to know anything about this class which means it can handle any possible feature descriptor as long as it implements the org.vizir.Descriptor interface which itself does not define anything, but is derived from the java.io.Serializable interface. More information about how to add new types of feature descriptors to the VizIR framework can be found here: How to build and register a new feature descriptor.

The complete diagram of the implementation of the entities related to feature descriptors now looks like this:

Implementation of the entities related to feature descriptors.

The DescriptorContainer to DescriptorInfo relationship is a many to one relation and can therefore be handled entirely by Hibernate. The mapping file for DescriptorContainer is shown in Figure 5.

Figure 5. The XML mapping file for the DescriptorContainer entity.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">


<hibernate-mapping>
  <class name="org.vizir.persistence.hibernate.DescriptorContainerImpl" table="Descriptor">
    <id name="id" type="long">
      <generator class="vm"/>
    </id>

    <property name="descriptor" column="data" type="org.vizir.persistence.hibernate.ObjectBlobType"/>

    <many-to-one name="descriptorInfoImpl" column="descriptorInfo_id"/>
  </class>
</hibernate-mapping>

Implementaion of the entities related to media objects

The implementation of the entities related to media objects is analogue to the implementation described in the previous sections. The following UML diagram illustrates this. All mechanisms for accessing the content of the media are excluded.

Implementation of the entities related to media objects.

In Figure 6 and Figure 7 the corresponding XML mapping files for the media entities are shown. They do not reveal anything new compared to the mapping files of the entities related to feature descriptors.

Figure 6. The XML mapping file for the Media entity.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">


<hibernate-mapping>
  <class name="org.vizir.persistence.hibernate.MediaImpl" table="Media">
    <id name="id" type="long">
      <generator class="vm"/>
    </id>

    <property name="name" type="string"/>
    <property name="description" type="string"/>

    <set name="collections" cascade="none" table="Media_to_MediaColl" inverse="true" lazy="true">
      <key column="media_id"/>
      <many-to-many column="collection_id" class="org.vizir.persistence.hibernate.MediaCollectionImpl"/>
    </set>
  </class>
</hibernate-mapping>

Figure 7. The XML mapping file for the MediaCollection entity.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">


<hibernate-mapping>
  <class name="org.vizir.persistence.hibernate.MediaCollectionImpl" table="MediaCollection" >
    <id name="id" type="long">
      <generator class="vm"/>
    </id>

    <property name="name" type="string"/>
    <property name="description" type="string"/>

    <set name="mediaObjects" cascade="none" table="Media_to_MediaColl" lazy="true">
	  <key column="collection_id"/>
	  <many-to-many column="media_id" class="org.vizir.persistence.hibernate.MediaImpl"/>
    </set>
  </class>
</hibernate-mapping>

Bibliography

[Gamma95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns. Addison-Wesley Publishing Company. 1995.