Transactional Document Business Objects
We discussed in the Transactional Documents 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 Client Framework Kuali Nervous System handled them could be standardized. For transactional documents, the document, by definition, does not perform a standard maintenance action; therefore, the Client Framework 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
Let us look at the class hierarchy, this will show that 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.kfs.module.cam.document.EquipmentLoanOrReturnDocument, for instance this one updates the asset equipment loan information. Most transactional documents are a bit more complex, however. A clean example of a bit more complex transactional document is the Asset Transfer: org.kuali.kfs.module.cam.document.AssetTransferDocument. That class is merely a regular Java class, save that it extends GeneralLedgerPostingDocumentBase meaning it will generate GL entries under certain circumstances). We can see that on AssetTransferDocument. Most of what it does is very normal POJO type stuff: it declares few properties, declares some getters and setters, and has some extra methods to deal with the GL generation.
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 EquipmentLoanOrReturnDocument:
<class-descriptor class="org.kuali.kfs.module.cam.document.EquipmentLoanOrReturnDocument" table="CM_EQPLNRTRN_DOC_T"> <field-descriptor name="documentNumber" column="FDOC_NBR" jdbc-type="VARCHAR" primarykey="true" index="true"/> <field-descriptor name="objectId" column="OBJ_ID" jdbc-type="VARCHAR" index="true"/> <field-descriptor name="versionNumber" column="VER_NBR" jdbc-type="BIGINT" locking="true"/> <field-descriptor name="loanDate" column="CPTLAST_LOAN_DT" jdbc-type="DATE"/> <field-descriptor name="expectedReturnDate" column="AST_EXPCTRETURN_DT" jdbc-type="DATE"/> <field-descriptor name="loanReturnDate" column="CPTLAST_LN_RTRN_DT" jdbc-type="DATE"/> <field-descriptor name="borrowerUniversalIdentifier" column="AST_BORWR_UNVL_ID" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerAddress" column="AST_BORWR_ADDR" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerCityName" column="AST_BORWR_CITY_NM" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerStateCode" column="AST_BORWR_STATE_CD" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerZipCode" column="AST_BORWR_ZIP_CD" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerCountryCode" column="AST_BORWR_CNTRY_CD" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerPhoneNumber" column="AST_BORWR_PHN_NBR" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerStorageAddress" column="AST_BORWRSTRG_ADDR" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerStorageCityName" column="AST_BORWRSTRGCT_NM" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerStorageStateCode" column="AST_BORWRSTRGST_CD" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerStorageZipCode" column="AST_BORWRSTRGZP_CD" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerStorageCountryCode" column="AST_BORWRSTRCNT_CD" jdbc-type="VARCHAR"/> <field-descriptor name="borrowerStoragePhoneNumber" column="AST_BORWRSTRPH_NBR" jdbc-type="VARCHAR"/> <field-descriptor name="capitalAssetNumber" column="CPTLAST_NBR" jdbc-type="BIGINT"/> <reference-descriptor name="documentHeader" class-ref="org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader" auto-retrieve="true" auto-update="object" auto-delete="object" proxy="true" > <foreignkey field-ref="documentNumber" /> </reference-descriptor> <reference-descriptor name="asset" class-ref="org.kuali.kfs.module.cam.businessobject.Asset" auto-retrieve="true" auto-update="none" auto-delete="none" proxy="true" > <foreignkey field-ref="capitalAssetNumber" /> </reference-descriptor> </class-descriptor>
First, and most importantly, every document in KFS, maintenance or transactional, has a document number; this is declared in DocumentBase. Therefore, EquipmentLoanOrReturnDocument 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.
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.kfs.fp.businessobject.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:
<!-- advance deposit specific collections --> <collection-descriptor name="advanceDeposits" proxy="true" element-class-ref="org.kuali.kfs.fp.businessobject.AdvanceDepositDetail" collection-class="org.apache.ojb.broker.util.collections.ManageableArrayList" auto-retrieve="true" auto-update="object" auto-delete="object"> <orderby name="financialDocumentLineNumber" sort="ASC" /> <inverse-foreignkey field-ref="documentNumber" /> </collection-descriptor>
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.
Kuali documentation is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.
Kuali software is licensed for use pursuant to the Affero General Public License, version 3.
Copyright © 2014 Kuali, Inc. All rights reserved.
Portions of Kuali are copyrighted by other parties as described in the Acknowledgments screen.
Kuali ® is a registered trademark of the Trustees of Indiana University.