HIBERNATE JBoss.org
 |  Register  | 
     
News 
About 
   Feature List 
   Road Map 
Documentation 
   Related Projects 
   External Documentation 
Download 
Forum & Mailinglists 
Support & Training 
JIRA Issue Tracking
Wiki Community Area


Hibernate Public Training Courses


Get Hibernate in Action eBook!


JavaWorld 2003 Finalist


Jolt Award 2004 Winner
      
Documentation > Community Area > HibernatePicofier - Using an Interceptor to Inject Dependencies into Persistent Objects

HibernatePicofier - Using an Interceptor to Inject Dependencies into Persistent Objects

Using Pico in a Hibernate application is pretty straight forward. Basically, you register your SessionFactory and Session as Pico components, and any objects that depend on these components declare a constructor that takes them as parameters.

However, how to you use Pico to inject dependencies into persistent entities instantiated by Hibernate? Hibernate calls the default constructor of an entity when it loads it from the database. How do we trick it out to use Pico to configure the entity instead? Answer: We use an interceptor.

Lets take a concrete example. Let's say we have a persistent entity called 'Order'. We have a mapping (.hbm.xml) file for it, and persist it using Hibernate. An Order needs a 'TaxCalculator' to perform some of its duties. We don't want to hard code in the concrete TaxCalculator class; instead we want to pass a TaxCalculator to the Order constructor. Something like this:

public class Order {

  private TaxCalculator taxCalculator;
  private Serializable id;

  public Order(TaxCalculator taxCalculator) {
    this.taxCalculator = taxCalculator;
  }

  public double getTaxOnOrder() {
    // use taxCalculator to compute tax...
  }

  //...
}

The problem is that when Hibernate instantiates an Order, it calls the default constructor. To trick it out, we can use a custom Interceptor that tells Hibernate how we want it to instantiate entities. Here it is:

/**
 * Uses Pico to inject dependencies into entities, as they are loaded by
 * Hibernate.  The <code>instantiate</code> method uses Pico to instantiate
 * the entity as a transient Pico component.  The entity constructor should
 * declare parameters for all Pico componenents it needs.  The entity constructor
 * should <i>not</i> include the key.  The <code>instantiate</code> method will
 * set the key (using reflection).  This approach simplifies Pico configuration.<p>
 * 
 * All other methods of this interceptor return their default values.
 */
public class HibernatePicofier implements Interceptor {

  private MutablePicoContainer container;
  private SessionFactory sessionFactory;

  /**
   * Creates a new <code>HibernatePicofier</code> object.
   * 
   * @param container the Pico container.
   * @param sessionFactory the Hibernate session factory.  This is used to 
   *        get meta data information, including the id property name, of
   *        new entities.
   */
  public HibernatePicofier(
    MutablePicoContainer container,
    SessionFactory sessionFactory) {
    this.container = container;
    this.sessionFactory = sessionFactory;
  }

  /**
   * Instantiates a new persistent object with the given id.
   * Uses Pico to inject dependencies into the new object.
   *
   * @param serializable the id of the object.
   * @return the newly instantiated (and Picofied) object.
   * @throw CallbackException if an error occurs
   */
  public Object instantiate(Class clazz, Serializable id)
    throws CallbackException {
    MutablePicoContainer tempContainer = new DefaultPicoContainer(container);
    tempContainer.registerComponentImplementation(clazz);

    Object newEntity = tempContainer.getComponentInstance(clazz);
    ReflectionUtil.setProperty(newEntity, getIdProperty(clazz), id);
    return newEntity;
  }

  private String getIdProperty(Class clazz) throws CallbackException {
    try {
      return sessionFactory.getClassMetadata(clazz).getIdentifierPropertyName();
    }
    catch (HibernateException e) {
      throw new CallbackException(
        "Error getting identifier property for class " + clazz,
        e);
    }
  }

  // all other methods just have dummy implementations that just
  // return default values...
}

We don't need to register the new object with the Pico container permanently, so we create a temporary child container and register the class of the object we are going to instantiate with that container. Then we ask that Pico container to instantiate the object for us, injecting any dependencies.

We also set the persistent id of the new object (using a simple reflection utility class not shown in this example - does Hibernate have such a utility?). It would be more Picotically correct to instead also pass the id to the Order constructor, but that would complicate the Pico configuration, as we'd have to register each entity class with Pico using Parameters.

To use this interceptor, you would configure your Pico container, and pass the interceptor to the the SessionFactory.buildSession method, like this:

  MutablePicoContainer container = new DefaultPicoContainer();
  container.registerComponenentImplementation(TaxCalculator.class, TaxCalculatorImpl.class);

  Configuration cfg = new Configuration();
  cfg.configure();
  SessionFactory factory = cfg.buildSessionFactory();    
  Interceptor picofier = new HibernatePicofier(container, factory);
  Session session = factory.openSession(picofier);

Any Order objects loaded from the session or queries would automagically get supplied with their TaxCalulator instance. If you want to write a unit test for Order, you can directly pass in a mock TaxCalculator object to the Order constructor, without messing with Pico.

Steve Molitor

smolitor@erac.com

      

coWiki