Customising Inserts using entity context

A common customisation pattern is that you want to store additional information on the application database whenever a Cúram entity is inserted. In "classic" Cúram you might have extended the out-of-the-box entity but this is discouraged for code constructed using Persistence Infrastructure because of the undesirable dependencies it creates between custom code and out-of-the-box code. Instead, you'll create a whole separate entity that gets updated in synch with the original.

Let's take a typical use case. A method of a façade class is called by the Cúram client to insert data collected on a UIM page. The façade method gets data from its parameters, and invokes service layer APIs to create a new entity instance and persist it. You want to collect additional information and persist it on a new entity along with the original, using the same primary key value.

The initial steps you will take are as follows, and are the same as described for "classic" Cúram code in the Cúram Server Developer's Guide:
The remaining steps are particular to code using the Persistence Infrastructure:

Here's how that works in practice. In order to keep the program listings concise we assume that you've already declared a new "classic" entity called MyAdditionalEntity and have extended the façade parameters to take the new details.

Here's the original façade:

Figure 1. A façade which stores MyEntity
...
public class MyFacade {
  @Inject
  protected MyEntityDAO myEntityDAO;

  public void createMyEntity(final MyEntityDetails details) throws
      AppException, InformationalException {
    MyEntity myEntity = myEntityDAO.newInstance();
    setDetails(myEntity, details.dtls);
    myEntity.insert();
  }

  protected void setDetails(final MyEntity e,
      final MyEntityDtls dtls)
      throws AppException, InformationalException {
    e.setFirstname(dtls.firstname);
    e.setSurname(dtls.surname);
  }

}

Here's our override of the façade:

Figure 2. A façade subclass which uses entity context
...
public class MyCustomFacade extends
  curam.custom.facade.base.MyCustomFacade {

  @Override
  public void createMyEntity(final MyEntityDetails details)
      throws AppException, InformationalException {
    MyEntity myEntity = myEntityDAO.newInstance();
    setDetails(myEntity, details.dtls);

    /*
     * Store additional details in entity context
     */
    myEntity.getContextContainer().put(MyAdditionalEntityDtls.class,
        details.additionalDtls);
    myEntity.insert();
  }

}

Here's our listener for inserts on the original entity. Note the handling when we find that no entity context has been passed. This is a design decision that must be made in each case - do we store blank additional details, or do we store nothing. If we choose to store nothing, then the application must know how to handle the situation later when we retrieve an entity and there are no additional details to be read.

Of course we know that there will always be context if the insert that is occurring was triggered via the façade we've just customized. But we always have to cater for the situation where the insert is occurring on code other than our façade.

Figure 3. A listener for inserts on MyEntity
@Singleton
class MyEntityListener extends PersistenceEvent<MyEntity> {
  /**
   * After MyEntity is inserted, also insert MyAdditionalEntity.
   */
  @Override
  public void postInsert(final MyEntity e) throws AppException,
      InformationalException {

    /*
     * Retrieve the stored details from entity context
     */
    MyAdditionalEntityDtls dtls = e.getContextContainer().get(
        MyAdditionalEntityDtls.class);

    /*
     * Note - don't store null details; on reads, the application
     * must handle having no additional details for a MyEntity
     * instance
     */
    if (dtls != null) {

      /*
       * Use same id as original entity
       */
      dtls.id = e.getID();

      /*
       * Insert additional details
       */
      MyAdditionalEntity additionalEntity =
        MyAdditionalEntityFactory.newInstance();

      additionalEntity.insert(dtls);
    }
  }
}

Here's how we register our listener:

Figure 4. A Guice module to register the listener in the previous listing
public class MyModule extends AbstractModule {

  @Override
  protected void configure() {

    /*
     * Get the listener set
     */
    Multibinder<PersistenceEvent<MyEntity>> myEventListeners =
      Multibinder.newSetBinder(binder(),
        new TypeLiteral<PersistenceEvent<MyEntity>>() {/**/});

    /*
     * Add a listener
     */
    myEventListeners.addBinding().to(MyEntityListener.class);
  }
}

In summary, what we've done is to provide a listener which receives insert events for one entity and performs inserts on another, supplemental entity. The data for the supplemental entity was piggybacked on "entity context", and will normally have been provided via a façade. However, it's important to note that this listener pattern works no matter where the insert was invoked from, although you'll find you have to decide how to handle the situation where an insert was performed but no entity context was provided.