This page describes the process for correctly implementing a Business Object with updatable collections. An updatable collection is a collection of a BO that will persist changes made to the collection when the parent BO is saved.
For this page, the word "delete" will mean the deletion of a collection element from the database (i.e. running a SQL DELETE statement). The word "remove" will mean the removal of an element from a collection on a browser. For example, when a user is presented with transactional document with a collection of items, the user may choose to remove an item before saving a document. Note that the button on the document may be marked with the word "delete", but this differentiation of terminology will make this discussion terse. In other words, "remove" refers to an act of the user, and "delete" refers to a database action. Removal from the form should result in deletion from the database.
Because of Rice's request-based architecture, when a form is submitted from the user to the server, we are only able to determine which elements are on the form. In particular, by looking at the request parameters alone, we can only know which elements are in a collection and are unable to determine whether the user removed an element from a collection. Because of this, OJB natively takes care of updating or adding items to a collection, but is unable to automatically determine which elements to delete from the database.
To implement this functionality, Rice retrieves the BO from the database and compares it with the BO from the form. It deletes any element BOs that exist in the database BO and not in the form BO (because it presumably has been removed). This page describes the process to enable OJB deletion of removed elements from a collection for a particular BO.
This process consists of two steps:
- Defining which updatable collections have deletable elements
- Implementing removed element deletion code if necessary
Defining which updatable collections have deletable elements
org.kuali.core.bo.PersistableBusinessObject.buildListOfDeletionAwareLists() should be implemented for all business objects that require deletion of removed elements.
In a nutshell, this method returns a
Collection of a BO. It returns a list of references to the actual collections storing data. Given a BO from the form to persist, it retrieves the corresponding BO from the database (based on primary key). Compares the collections returned by
buildListOfDeletionAwareLists(), and deletes any elements contained within the DB-based BO and not in the form-based BO.
For each BO class, its implementation of
buildListOfDeletionAwareLists() should return a list of equal size, and each element of the list must represent the same property of the BO every time it is called. Note that this does not imply that all BOs (regardless of class) must return a list with the same size. It implies that, for a particular BO class and primary key combination, it always has to return a list of the same size and matching collections.
The following is an example implementation for accounting documents, which are business objects. Most accounting documents contain 3 updatable collections. Two of them, the source and target accounting line collections, must be able to delete accounting lines from the collection. Most of them also contain a list of General Ledger Pending Entries. These are dynamically generated by the accounting framework and not directly editable by users, so we don't need to enable deletion of removed elements.
This implementation says that the source and target accounting line lists should delete elements that are removed for a particular business object. Unless overridden, note that calling this method will always return a list of 2 collections, the first element is the source accounting lines collection, and the second is the target accounting lines collection.
Implementing removed element deletion code if necessary
If the BO containing deletable collections will be persisted using a Spring bean of types
DocumentDaoOjb, then your work is done. DAOs should only be called by services. For higher level code (e.g. struts actions), use
DocumentService, which delegate to these DAOs. There may be other services that use these DAOs as well.
However, if it is not possible to directly or indirectly use these DAOs, then the developer should implement a new DAO.
The steps for doing so are as follows:
- Have the DAO class implement
- In the method that saves/updates the business object (e.g. save), retrieve the corresponding business object from the database with the same primary key values.
- Retrieve the
org.kuali.core.util.OjbCollectionHelperbean from the Spring bean factory. In rice, use
KNSServiceLocator.getOjbCollectionHelper()and in KFS or Rice client code, use
org.kuali.core.util.OjbCollectionHelper.processCollections(OjbCollectionAware theDAO, PersistableBusinessObject boFromForm, PersistableBusinessObject boFromDatabase)
- Store the BO from the form as normal (typically by calling