We discussed in the introduction that while a maintenance document maintained data in some way, a transactional document performs an action. Because maintenance documents carry out standard actions, how the Kuali Nervous System handled them could be standardized. For transactional documents, the document, by definition, does not perform a standard maintenance action; therefore, the Kuali Nervous System cannot have quite the same level of standardization for these documents. One of the upshots is that while the Nervous System handles the persistence of maintenance documents while they go through workflow, it needs a transactional document to handle its own persistence. And KNS does that by having a transactional document be a business object itself.
How to create a business object is well documented in our cookbook already, so let's concentrate on how creating the business objects that are transactional documents is special.
- Creating Transactional Document business object
- Adding collections to Transactional Documents
Creating Transactional Document business object
A transactional document is simply a document which extends org.kuali.core.document.TransactionalDocumentBase. TransactionalDocumentBase extends org.kuali.core.document.DocumentBase, which in turns extends org.kuali.core.bo.PersistableBusinessObject. Huh! This means that a transactional document simply is a persistable business object. If our new document is a transactional document, then it simply is a persistable business object by default. Nice to know. As such, a transactional document is expected to have certain attributes which will be stored in the database.
There are some very simple transactional documents out there - org.kuali.module.cg.bo.Close, for instance, shows that a transactional document need not be complicated. Most transactional documents are a bit more complex, however. A clean example of a bit more complex transactional document is the Advance Deposit: org.kuali.module.financial.document.AdvanceDepositDocument. That class is merely a regular Java class, save that it extends CashReceiptFamilyBase (which itself is a descendant of TransactionalDocumentBase). We can see that on AdvanceDepositDocument. Most of what it does is very normal POJO type stuff: it declares three properties, declares some getters and setters, and has some extra methods to deal with the advanceDeposits collection. It's pretty basic stuff. Things start to get a bit more complex when we dive into the OJB mapping for our transactional document/business object.
Doing the OJB Mapping
Every transactional document, since it is, ultimately, a persistable business object, needs to have an OJB mapping. Unlike maintenance documents, which persist themselves automatically, transactional documents require this extra step. However, the mapping looks a lot like OJB mappings for common business objects does. This, for instance, is the mapping for AdvanceDepositDocument:
The mapping declares nine attributes. We remember "totalAdvanceDepositAmount" and "nextAdvanceDepositLineNumber" from the AdvanceDepositDocument itself. We also remember "objectId" and "versionNumber," which come from PersistableBusinessObject. So what of the others?
First, and most importantly, every document in KFS, maintenance or transactional, has a document number; this is declared in DocumentBase. Therefore, AdvanceDepositDocument has one too. That document number MUST be stored in the database for the document. Therefore, for all transactional documents of all stripes, we need to declare this attribute in our OJB mapping. Also notice the reference-descriptor for the document header; this, too, is required.
The rest of our rag tag attributes come from the rest of the document hierarchy that extends TransactionalDocumentBase: "nextSourceLineNumber" comes from AccountingDocumentBase, "depositDate" comes from CashReceiptFamilyBase, and "postingYear" and "postingPeriodCode" come from LedgerPostingBase. LedgerPostingBase also requires us to declare the "accountingPeriod" reference-descriptor; the collections "sourceAccountingLine" and "generalLedgerPendingEntries" come from AccountingDocumentBase and GeneralLedgerPostingBase respectively; and finally, the collection "advanceDeposits," declared in the standard way, comes from...well, that comes from AdvanceDepositDocument. As we can see, it's important to know what we're extending, as it may require extra declarations in our OJB mappings. However, for basic transactional documents, all that's needed are the documentNumber, documentHeader, versionNumber, and objectId.
The transactional document as a Document
Since a transactional document extends TransactionalDocumentBase which, in turn, extends DocumentBase, every transactional document gets certain attributes from DocumentBase (such as documentNumber). The cookbook has information for the curious on workflow callbacks. There are also persistence layer callbacks inherited from PersistableBusinessObject that several document classes, including the venerable DocumentBase, override behavior for:
- prepareForSave() - called just before the document is saved. There's also a version of the method which is passed a KualiEvent as a parameter, which is declared in DocumentBase; it can be assumed that preparedForSave() is always called, and that prepareForSave(KualiEvent) is called if an event caused a save (such as when the user presses the "save" button on their document).
- processAfterRetrive() - called after the document is retrieved from the database
- postProcessSave() - called after a document has been saved
Adding collections to Transactional Documents
Most transactional document have some kind of collection; the majority of transactional documents in KFS are AccountingDocuments, which include collections of accounting lines. Indeed, some transactional documents are nothing more than document numbers and collections.
Collections in documents are exposed as properties...but there's a couple more methods than the traditional getter and setter. Let's take a look again at AdvanceDepositDocument. It defines a collection, advanceDeposits, a List of org.kuali.module.financial.bo.AdvanceDepositDetail records. AdvanceDepositDetail is a business object and follows all the business object rules: it extends PersistableBusinessObjectBase, and it has a business object data dictionary configuration as well as an OJB mapping. Then AdvanceDepositDocument creates the following five methods:
- getAdvanceDeposits(), which returns the whole list of AdvanceDepositDetail records
- setAdvanceDeposits(), which sets an entire list of AdvanceDepositDetail records
- addAdvanceDeposit(), which takes in as a parameter a new AdvanceDepositDetail and sticks it to the end of the List
- getAdvanceDepositDetail(), which takes in an index and returns the AdvanceDepositDetail at that index. In methods like these, if the index is larger than the size of the List, the List is populated with new, blank business objects until the record at the requested index is no longer null, and then it returns the new, blank record at that point
- removeAdvanceDepost() is given an index and removes the AdvanceDepositDetail record at that index
This leaves just the OJB Mapping for the relationship, though taking a second look would likely be informative:
The collection should be named after the getter and setter for the whole list. In this case, since the getter for the whole list is "getAdvanceDeposits()", OJB knows the property name is "advanceDeposits". Because auto-retrieve is set to true and auto-update is set to "object", OJB will manage the collection for us, retrieving all AdvanceDepositDetail records for a document when a document is loaded from the persistence store and saving all AdvanceDepositDetail records when the document is saved to the persistence store.
This leaves us one piece of business: the data dictionary. Sadly, we'll defer that until later. Suffice it for now to say that while business objects in collections on transactional documents have business object data dictionary entries, the business objects that are the transactional documents themselves have all of their data dictionary configuration in the document data dictionary file.