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.
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:
... 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:
... 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.
@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:
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.