In the Kuali Nervous System, there are two kinds of documents: maintenance documents - which handle simple business object operations such as creation, editing, and copying, and transactional documents - which tend to complete a large action, a "transaction", when the document is finalized. It makes sense, then, that these two documents have separate and distinct data dictionary vocabularies. However, since maintenance documents and transactional documents are, indeed, both documents, there are a lot of similarities as well, and this document covers the shared vocabulary between maintenance document and transactional document data dictionary entries.
- Rules and Pre-Rules
- The Document Authorizer and Authorizations
- Labels and Help
- Document Types
- Workflow Serialization
- Determining which document properties to serialize
- How to specify which attributes are required
- How to specify "serialize everything"
- How to specify "serialize nothing"
- How to serialize the main business objects of a maintenance document
- How to specify "serialize information about the document initiator"
- How to specify "serialize all of the document's primitives"
- The KualiPaymentReasonAttribute example from above
- How to serialize all elements of a collection on a document
- How to serialize the reference object of a collection element
- How to serialize multiple properties of a document
Rules and Pre-Rules
While rule classes are fully covered on the Rules and Pre-Rules pages, it's important for us to note that it is the data dictionary where rules are actually tied to documents. The code to accomplish this is easy, as we can see from the data dictionary for the Budget Adjustment.
Both the businessRulesClass and preRulesCheckClass attributes are optional. If no rule class is specified, then Maintenance Documents and Transactional Documents both default to their base rule classes - org.kuali.core.rules.MaintenanceDocumentRuleBase and org.kuali.core.rules.TransactionalDocumentRuleBase, respectively.
The Document Authorizer and Authorizations
The document data dictionary is also where we tie in the document authorizer and the authorizations tags, the instructions about who and how KFS users are allowed to access given documents. Unlike rules and pre-rules, both of these tags are required.
This is the class name for the document authorizer to use. Here's how it looks for the Advance Deposit document:
What if we don't have a document authorizer? Then, we would probably just use org.kuali.core.document.authorization.TransactionalDocumentAuthorizerBase as the document authorizer.
We can do several actions with the authorization tag. The most popular use of the tag is to declare which workgroups can initiate a document, such as this use in Advance Deposit document:
The "authorizations" tag works kind of funny. Here, we can declare as many "authorization" tags as we want within the "authorizations" tag. Each "authorization" tag has an action attribute, with the name of a given action, and then the workgroups which are allowed to perform that action. All of these authorizations, then, get stored by the AuthorizationService. It is up to the DocumentAuthorizer class to make use of the authorizations that were stored. DocumentAuthorizerBase, in its "canInitiate()" and "canCopy()" methods asks the AuthorizationService if a given user is a member of a workgroup with an "initiate" action for the given document type. If, for instance, a user attempted to initiate an Advance Deposit document, AdvanceDepositDocumentAuthorizer would ask the AuthorizationService if the user was the member of the "kualiUniversalGroup" workgroup, because that's the workgroup associated with the "initiate" action. Of course, every user in the system is automatically associated with the kualiUniversalGroup, so any KFS user can initiate an Advance Deposit document.
There are two important points to consider here. First of all, we can declare any action we want for an authorization - as long as the document authorizer for our document knows to ask AuthorizationService about it. Second of all, for basic document authorizations to work, we need to declare the "initiate" action authorization, just as Advance Deposit does.
There are two other actions in use in the system, both used exclusively by Research Administration documents: view and modify. The sky is the limit on this, though.
Labels and Help
A number of tags in the data dictionary for documents are simply to provide helpful text for the user. They are: label, summary, description, and help.
The label tag has the name that the document will show when it appears to the user. So, for the Advance Deposit document, we want the user to see "Advance Deposit" in the document header. Hence the declaration:
label is a required tag.
We can also add helpful summary and description for the document. This feature is so helpful, in fact, that many documents simply put junk data in those tags. However, the summary should be a short description of the document and the description should be a bit more helpful. Advance Deposit has an excellent description at the very least:
Neither tag is required.
Finally, the help tag ties to the document to a help parameter. A help parameter is a system parameter, where the value is a URL for the help document for the given document. Here's the declaration that the Advance Deposit data dictionary uses:
In the KFS default data set, that parameter points to the following URL: default.htm?turl=WordDocuments%2Fadvancedepositdocument.htm. When this tag is present in the data dictionary, the data dictionary puts the question mark icon by the name of the document, and when the user clicks that, the given url in the parameter is opened, hopefully explaining the document. The help tag is not required.
Both the workflow document type name and the document type code for a document must be declared in the data dictionary next. Here's the example from the Advance Deposit Document:
When an action is performed upon the document that requires workflow (e.g. saving, routing, approving, etc.), the document is serialized into an XML string, which is then passed onto the workflow to be used for performing searches and computing action requests. Basically, the XStream serialization mechanism traverses through the whole object graph, serializing each element. For example, for an Internal Billing, the serializer would serialize the source and target accounting line lists, any of their references, and their references, etc. Obviously, this could lead to very large XML data being generated, causing all sorts of performance problems both within KFS and workflow. Also, most of the information within this XML would not be used by the workflow engine.
This page will describe a data-dictionary based mechanism of specifying which objects within the object graph should be serialized. By doing so, the generated XML will be smaller and faster to process.
However, before we start describing how to specify which objects to serialize, we must first determine which objects need to be serialized. Furthermore, we must know where this object is in the object graph relative to the document.
Determining which document properties to serialize
Each document corresponds to a given document type. The document type determines how the workflow engine will attempt to extract values from the serialized XML form of the document.
- Attributes, listed under the "Document Type Searchable Attributes" box. These attributes can be used for performing document searches and are defined under the <attributes> tag of the doc type config file.
- Templates, defined within the
<routeNodes>tag of the workflow XML config file
- Splits, defined within the
<routeNodes>tag of the workflow XML config file as well (see here)
Using the rule template lookup (under the admin tab), we see that a template may consist of 0 or more rule attributes (e.g. KualiAccountTemplate uses the KualiAccountAttribute).
So as far as we're concerned, a doc type is associated with attributes (either directly associated to the doc type, or indirectly associated through a template), and some split nodes. For our purposes, it does not matter whether the doc-type-to-attribute association is direct or not.
About workflow inheritance
Each workflow doc type configuration file may contain these three tags:
<attributes>. If a config XML file is missing any of these tags, it will use the corresponding tag from its parent document type (or the parent's parent, if the parent is missing tags, and so on). So it is sometimes necessary to look through the whole document hierarchy to figure out the Attributes, Templates, and Splits for a document.
Attributes are responsible for extracting values from a document and making them available to be searched or routed upon. There are 2 types of attributes, based on how they're configured:
- XML based
- Java code based
To gather more information about attributes, use the "Rule Attributes" link under the admin tab. XML-based attributes have the string "Xml" in their names.
XML based attributes
XML-based attributes use XPath to extract a value from the workflow XML file. This makes it very easy to figure out which attributes it needs. Using the rule attribute lookup, we are able to get the configuration data for the KualiPaymentReasonAttribute, used for the disbursement voucher.
XPath expressions are contained within the
wf:xstreamsafe functions, so in our example, we know that this attribute requires the DV payment reason code of the dvPayeeDetail property of the document.
Note that many of the XML based attributes use the
org.kuali.workflow.attribute.AccountLineReplacingXmlRuleAttribute rule attribute class. Recall that when XStream serializes a
Collection, each collection element is wrapped with a tag named with the class name of the element (See further down the page for examples). So, for accounting lines, this means that each accounting line is wrapped with a tag named with the accounting line class name. This
AccountLineReplacingXmlRuleAttribute implementation automatically replaces a special substring in the XPath expression with the source or target accounting line appropriate to the document. For example, the KualiFundGroupAttribute attribute has the following configuration:
Note that there are 2 XPath expressions contained within the
wf:xstreamsafe functions. Before these XPath expressions are evaluated, "kfs_sourceAccountingLineClass" and "kfs_targetAccountingLineClass" are replaced with the strings representing the source and target accounting line class names, respectively, of the document. These line class names can be accessed by calling
Many java-based attributes dynamically generate XPath expressions to evaluate against the XML file. For example, based on the doc type, it might look for an account number in different places because the document structure is different.
The following code is how KualiAccountAttribute extracts accounting numbers from an accounting document (constants have been replaced by literals):
So, what this attribute will do is to look for the chart code and account number under the tag containing the source or target accounting line class name for a given accounting document type. A vast majority (but not all) of the accounting document classes have fields (in the
java.lang.Field sense) representing collections of accounting lines. So, for a vast majority of accounting documents, we know that we need to serialize each primitive of element in the accounting lines collections (for a definition of primitive and collection, see here.
Split nodes are defined with a class name. Therefore, like java-based attributes, we need to read through the code to determine which document properties it uses to determine whether to branch.
How to specify which attributes are required
After analyzing the attributes and split nodes associated with a document, you should have an idea of which properties it requires from the XML file and where that property is in the object graph, relative to the document.
To specify references, a section indicated with the
<workflowProperties> tag needs to be included near the end of the DD file (consult the DTD file for exact positioning). This element indicates that the DD will specify which attributes to serialize.
<workflowProperties> tag, zero-or-more
<workflowPropertyGroup> tags may be specified, and each of these tags has an optional
basePath attribute. This base path represents the absolute path of a property from the root object. All primitives of the property at the base path will be serialized. If the base path is not defined, then the base path will be assumed to be the path to the document, and all of the document's primitives will be serialized.
<workflowPropertyGroup> tags, zero-or-more
<workflowProperty> tags may be specified, and each tag has a required
path attribute. This path is relative to the base path defined in the enclosing
<workflowPropertyGroup>. Typically, the last element in this path will refer to a business object or a collection; however, the last element in the path may also refer to a primitive value.
About Serializing UniversalUser instances
UniversalUser are unique because from the API's point of view, it contains a Map of module code to module user
TransientBusinessObjects. However, there are 2 Maps within the object that are used to dynamically build these module users, and XStream uses reflection, meaning that getter methods are not called. See here for an example about how user objects are serialized.
In general, you will not be able to serialize references from module user object, but you will be able to serialize the foreign keys to any reference tables so that a workflow attribute will be able to access it (look at the moduleProperties map in the example output below).
How to specify "serialize everything"
Don't add any
<workflowProperties> section to the data dictionary file.
How to specify "serialize nothing"
Specify the following immediately above the closing tag for
This generates an output XML similar to the following:
How to serialize the main business objects of a maintenance document
The structure of maintenance documents is unique compared to transactional documents. The document object contains an
newMaintainableObject, which are instances of
Maintainable. To access the business object being maintained, one would need to create a
<workflowPropertyGroup> with an empty base path as well as
<workflowProperty> tags with a path of "oldMaintainableObject.businessObject" (for the BO on the "old" side of the document) and "newMaintainableObject.businessObject" (for the BO on the "new" side).
How to specify "serialize information about the document initiator"
In the base implementation, the root object being serialized is an instance of
KualiDocumentXmlMaterializer. The initiator is the
kualiTransactionalDocumentInformation.documentInitiator.universalUser property of that object, so we can define a
<workflowPropertyGroup> with that base path.
How to specify "serialize all of the document's primitives"
This generates an output XML similar to the following:
The KualiPaymentReasonAttribute example from above
We know from the discussion of KualiPaymentReasonAttribute that we need to serialize the dvPayeeDetail object of document.
How to serialize all elements of a collection on a document
In this example, we'll serialize the source accounting lines of a document (as well as the document primitives, because it's implicit in the <workflowPropertyGroup> tag).
How to serialize the reference object of a collection element
Note that this example skips the primitives of the accounting line list element.
How to serialize multiple properties of a document
To serialize multiple properties of a document, just specify multiple
<workflowProperty> tags under the
<workflowPropertyGroup>. In this example, we are serializing the primitives of the following properties:
- the document object (
- the document header (
<workflowProperty path="documentHeader" />)
- each target accounting line (
<workflowProperty path="targetAccountingLines" />)
- each source accounting line (
<workflowProperty path="sourceAccountingLines" />)
- the account of each target accounting line (
<workflowProperty path="targetAccountingLines.account" />)
- the account of each source accounting line (
<workflowProperty path="sourceAccountingLines.account" />)