Skip to end of metadata
Go to start of metadata

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

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.

documentAuthorizerClass

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.

authorizations

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.

Document Types

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:

The documentTypeName tag holds the name of the KEW document type and the document type code holds the code of the financial document type.

Workflow Serialization

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

Icon

The word "attribute" in this document refers to a workflow attribute, explained below, and not to an attribute of a document. To refer to the attribute of a document, the word "property" is used.

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.

There's pretty extensive documentation about workflow doc types here and here, but for our purposes, we need to know that a doc type may contain any or all of the following:

  1. 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.
  2. Templates, defined within the <routeNodes> tag of the workflow XML config file
  3. 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: <routePaths>, <routeNodes>, and <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

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:

  1. XML based
  2. 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 org.kuali.workflow.attribute.AccountingLineClassDeterminer.getSourceAccountingLineClassName() and org.kuali.workflow.attribute.AccountingLineClassDeterminer.getTargetAccountingLineClassName().

Java-based attributes

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.

Icon

Note that in the attribute's code, you may see the type "Document". You MUST be aware of whether "Document" refers to org.w3c.dom.Document or org.kuali.core.document.Document (use eclipse to help you out by hovering the mouse over the type when it appears in the code).

If it's referring to the DOM document, then that means that it's extracting information from the XML.

If it's the Kuali document, then that means that it's extracting information from the actual document from the document service. In which case, it means that the attribute does not require any information from the XML file to function properly.

Split nodes

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.

Icon
  • Note that some split node implementations use the document service to retrieve an actual copy of the document as a java object. In these cases, they don't require any data from the XML file.
  • Note that in the class's code, you may see the type "Document". You MUST be aware of whether "Document" refers to org.w3c.dom.Document or org.kuali.core.document.Document (use eclipse to help you out by hovering the mouse over the type when it appears in the code).

How to specify which attributes are required

What are primitives, business objects, and collections?

Icon

The document serializer makes a distinction among primitives, business objects, and collections. Therefore, it is helpful to explain what they are, in the context of the document serializer.

  • A business object is anything that implements BusinessObject.
  • A collection is anything that implements java.util.Collection.
  • A primitive is anything else, including null. When a primitive is serialized, all of its primitives (and all nested primitives) are serialized as well.

Some information about the serialization process

Icon

The root of the XML is not the tag representing the document. Instead, the base implementation of KFS/Rice wraps a document in an instance of KualiDocumentXmlMaterializer, and the document is accessible as the "document" property of this wrapper. The wrapper also provides references to information about the document initiator, and institutions may decide to place additional metadata in a different wrapper and serialize it instead. See Document.wrapDocumentWithMetadataForXmlSerialization().

About XStream

Icon

By default, XStream uses reflection to access properties of an object, rather than introspection. This means that a document cannot count on its getter methods being called when property values are being extracted.

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.

Within the <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.

Within each <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.

Path syntax

Icon

The paths used for the document serializer is similar to POJO property names, except that collections are treated like regular primitives. For example, the following says "serialize all primitives of the account object referenced by the source accounting lines":

Even though sourceAccountingLines is a java.util.List, it is treated as a simple property (i.e. there are no indices). Whenever a collection is encountered on a path string, all of collection's elements are serialized. If the name of the collection is the last token in the path string, then all primitives of all collection elements are serialized.

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).

Examples

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 </transactionalDocument> or </maintenanceDocument>.

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 oldMaintainableObject and 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).

Output:

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.

Output:

How to specify "serialize all of the document's primitives"

Icon

The specification of <workflowPropertyGroup/> (with no basePath attribute) will automatically serialize all of a document's primitives, regardless of whether there are nested workflowProperty tags underneath.

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.

Output:

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).

The output:

How to serialize the reference object of a collection element

Note that this example skips the primitives of the accounting line list element.

Output:

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 (<workflowPropertyGroup>)
  • 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" />)

Output:

  • No labels