Granular Validations

A lot of important business logic within KFS is wrapped up in Accounting Documents. A major push was made in KFS to make this business logic more easily changed by implementing institutions. This new validation system builds on the concepts in the traditional KNS validation system, but leverages Spring to treat validations more interchangeably.

Steps for validating a document 

Before we begin in earnest, here's a check list of the steps we need to do, in order, to set up validations for an accounting document. Each of these steps is covered both in more detail and with an augmented sense of narrative flair in the other sections.

  1. First, create validation beans. This is covered in detail later in this document.
  2. If the document's module does not have a module/{module name}/document/validation/configuration/{module name but nicely upper cased}ValidatorDefinitions.xml, one needs to be created. This Spring XML configuration will be the repository of the definitions for the validation beans we just created. All of these beans, though, should be abstract until we tie them into our event-based validations.
  3. Create in module/{module name}/document/validation/configuration a Spring XML configuration named {document name}Validation.xml. This is where we set up the validations as they relate to the events they are validated against.
  4. In the document's module's spring-{module name}.xml file, we'll write import statements for all the new Spring files created so far.
  5. In the data dictionary configuration for the document, we'll do the following:
    1. Create a validation map that maps event classes to the names of the beans that should validate that event
    2. Set the validationMap property of the main document configuration bean to have a ref to the validation map bean we just created
    3. And finally, set the businessRulesClass to org.kuali.kfs.rules.AccountingRuleEngineRuleBase.

That's it! Let's take a look at the details.

Creating validation beans 

In the KNS rules model, rules are associated with events. Every event has a corresponding interface which a rules class must implement to be able to handle events of that type. So, for instance, the RouteDocumentEvent is associated with the RouteDocumentRule and when fired, the event calls processRouteDocument - the method required by the RouteDocumentRule method - on the rules class. This meant that all the business logic for the rule was called, at least, from a single, monolithic point, and while developers tried to break up rules in a way that they could be overridden, there was no good way to add rules at a top level without inheriting a rules implementation and extending the rules method.

The new Accounting Document Rules Engine takes a different tack. It splits rules into very simple validations. These simple validations are then declaratively configured via Spring. If an implementing institution wants to change the rules, they need only write the extra rulettes they're interested in and change the declared configuration. While there's a lot of extra configuration involved in this process, we've gained a huge amount of flexibility in the process.

What are the rules for creating a new validation class?

  1. The class implements the org.kuali.kfs.validation.Validation interface (though it's typically easier to simply extend org.kuali.kfs.validation.GenericValidation)
  2. The class mplements the public boolean validate(AttributedDocumentEvent event) method. This method should contain all of the business logic for the validation: the business logic test, the setting of errors, and anything else the Validation needs to do.
  3. The class has setter properties for any information they need to be given from the triggering event to complete the validation. For instance, most Validation classes contain a "setAccountingDocumentForValidation" method.

Let's take a look at a real Validation, org.kuali.kfs.sys.validation.impl.BusinessObjectDataDictionaryValidation, which takes any business object and performs the standard data dictionary validations against it.

org.kuali.kfs.sys.validation.impl.BusinessObjectDataDictionaryValidation.java
/**
 * A validation to have the data dictionary perform its validations upon a business object
 */
1.  public class BusinessObjectDataDictionaryValidation extends GenericValidation {
2.    private DictionaryValidationService dictionaryValidationService;
3.    private PersistableBusinessObject businessObjectForValidation;

      /**
       * Validates a business object against the data dictionary
       * @see org.kuali.kfs.validation.GenericValidation#validate(java.lang.Object[])
       */
4.    public boolean validate(AttributedDocumentEvent event) {
5.      return getDictionaryValidationService().isBusinessObjectValid(businessObjectForValidation, "");
6.    }

      /**
       * Gets the dictionaryValidationService attribute. 
       * @return Returns the dictionaryValidationService.
       */
7.     public DictionaryValidationService getDictionaryValidationService() {
8.       return dictionaryValidationService;
9.     }

      /**
       * Sets the dictionaryValidationService attribute value.
       * @param dictionaryValidationService The dictionaryValidationService to set.
       */
10.   public void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) {
11.     this.dictionaryValidationService = dictionaryValidationService;
12.   }

      /**
       * Gets the businessObjectForValidation attribute. 
       * @return Returns the businessObjectForValidation.
       */
13.   public PersistableBusinessObject getBusinessObjectForValidation() {
14.     return businessObjectForValidation;
15.   }

      /**
       * Sets the businessObjectForValidation attribute value.
       * @param businessObjectForValidation The businessObjectForValidation to set.
       */
16.   public void setBusinessObjectForValidation(PersistableBusinessObject businessObjectForValidation) {
17.     this.businessObjectForValidation = businessObjectForValidation;
18.   }
19. }

This is pretty easy stuff: without the javadoc comments, a mere 19 lines of code! (Of course, we're helped by having a service do a lot of the validation for us in this particular case). We can see, too, that this class does everything we said above a Validation needs to do.

  1. It extends org.kuali.kfs.sys.validation.impl.GenericValidation - that means that it does indeed implement the org.kuali.kfs.validation.Validation interface.
  2. It overrides the public boolean validate(AttributedDocumentEvent event) method for its own unique logic - in this case, deferring to the DictionaryValidationService to do the validation
  3. It has a setter for the business object to be validated - in this case, the businessObjectForValidation property. As a way of differentiating properties to validate from other properties on the Validation, the suffix "ForValidation" is added to the names of these properties. We advise implementing institutions to continue this practice.

By passing values into the Validation by setters, we gain flexibility. We could conceivably cast the event argument passed into the validate method to a given event or event interface - but if we do that, then we're tied to a certain event. BusinessObjectDataDictionaryValidation, since it uses a setter, is not tied to any single event, which means it can be used to validate against practically any kind of event. Given that, the use of setting properties instead of pulling information directly from the passed event is highly recommended.

Next: let's see what the configuration looks like.

Configuring validation beans in Spring Files 

Because BusinessObjectDataDictionaryValidation is used in a lot of contexts, we'll cover its configuration in a little bit. First, let's look at a simpler validation: the org.kuali.kfs.sys.validation.impl.DebitsAndCreditsBalanceValidation. This validation is part of the basic accounting document routing validation - it checks that the debits and the credits of the general ledger pending entries on the document balance each other. It takes in one validation property: accountingDocumentForValidation.

The easiest way for us to leverage existing libraries to help with the configuration of these Validations was to use Spring. Each Validation is defined as a Spring bean. Let's look at the configuration for DebitsAndCreditsBalanceValidaton, from org/kuali/kfs/sys/validation/configuration/FinancialSystemValidators.xml:

<bean id="AccountingDocument-debitsAndCreditsBalanceValidation" class="org.kuali.kfs.sys.validation.impl.DebitsAndCreditsBalanceValidation" abstract="true" />

We note that this declaration makes the bean abstract. Since these Validation beans typically hold some kind of state, they have to be declared as prototypes - thus our original bean declaration is only half of the work. We need a concrete child of the validation parent, such as the example below.

1.  <bean parent="AccountingDocument-debitsAndCreditsBalanceValidation" scope="prototype">
2.    <property name="parameterProperties">
3.      <list>
4.        <bean parent="validationFieldConversion">
5.          <property name="sourceEventProperty" value="document" />
6.          <property name="targetValidationProperty" value="accountingDocumentForValidation" />
7.        </bean>
8.      </list>
9.    </property>
10. </bean>

What's different here? The setting of the parameterProperties. GenericValidation has getters and setters for a property called "parameterProperties". This is a list of org.kuali.kfs.sys.validation.impl.ValidationFieldConvertible implementations (the generic implementation is named org.kuali.kfs.validation.ValidationFieldConversion and has been created as an abstract bean named "validationFieldConversion"). This is what we use to get properties of the event triggering the validation which populate the validation bean. In this case, we have a sourceEventProperty named document - so we expect that there's a "getDocument()" property on the event that triggers this validation. We then populate that using the targetValidationProperty, "accountingDocumentForValidation" - which we knew was the validation property. All non-abstract versions of the bean must populate the Validation class's properties like this.

Since certain validationFieldConversion configurations are used over and over again, we simplified this a bit:

<bean id="accountingDocumentFieldConversion" abstract="true" parent="validationFieldConversion">
  <property name="sourceEventProperty" value="document" />
  <property name="targetValidationProperty" value="accountingDocumentForValidation" /> 
</bean>

<bean parent="AccountingDocument-debitsAndCreditsBalanceValidation" scope="prototype">
  <property name="parameterProperties">
    <list>
      <bean parent="accountingDocumentFieldConversion" />
    </list>
  </property>
</bean>

There are also preset validationFieldConversions for accountingLine to accountingLineForValidation, updatedAccountingLine to updatedAccountingLineForValidation, and others.

Note one other thing: this definition of the bean is scoped as "prototype" (ie, it is not a singleton bean). Since we set state on the bean, we need to set the scope to prototype. Spring will create a new bean every time the validation is run, and any state set on it will not affect any other beans.

Adding many validations together with CompositeValidations 

In routing our accounting document, we want to validate more than whether the debits and credits balance. But, of course, we want to break the Validations so that each Validations covers only one small and distinct fragment of validation logic. That means that we need to create validation beans that are actually many validation beans in one.

CompositeValidation implements the Validation interface. Its main job is to hold a list of other validations. When validate is called on CompositeValidation, it simply walks through the Validations in its validations property, validating for each. Let's look a full example, once again declared in FinancialSystemValidators.xml

1.  <bean id="AccountingDocument-RouteDocument-DefaultValidation" class="CompositeValidation" scope="prototype" abstract="true">
2.    <property name="validations">
3.      <list>
4.        <bean parent="AccountingDocument-requiredAccountingLinesCountValidation" scope="prototype">
5.          <property name="accountingLineGroupName" value="source" />
6.          <property name="minimumNumber" value="1" />
7.          <property name="parameterProperties">
8.            <list>
9.              <bean parent="accountingDocumentFieldConversion" />
10.           </list>
11.         </property>
12.       </bean>
13.       <bean parent="AccountingDocument-requiredAccountingLinesCountValidation" scope="prototype">
14.         <property name="accountingLineGroupName" value="target" />
15.         <property name="minimumNumber" value="1" />
16.         <property name="parameterProperties">
17.           <list>
18.             <bean parent="accountingDocumentFieldConversion" />
19.           </list>
20.         </property>
21.       </bean>
22.       <bean parent="AccountingDocument-debitsAndCreditsBalanceValidation" scope="prototype">
23.         <property name="parameterProperties">
24.           <list>
25.             <bean parent="accountingDocumentFieldConversion" />
26.           </list>
27.         </property>
28.       </bean>
29.     </list>
30.   </property>
31. </bean>

How does our composite validation work? In line 1 of the code sample, we can see that a bean is declared that uses as its parent the bean "CompositeValidation", which is a convenient declaration of org.kuali.kfs.sys.validation.impl.CompositeValidation. This class has one property set: a list of child validations, called validations (lines 2 and). In that list, we simply list the validation beans that we want validated. CompositeValidation goes through the list in the order specified in the configuration. Note, though, that CompositeValidation does not have its own parameterProperties - each sub-validation must declare its own parameterProperties as it needs.

What if, in this validation, we didn't want to check any other rulette if the first rulette failed - in this case, if the document didn't have the required number of source lines, we didn't want to validate the rulettes that checked if there were the required number of target lines or that the debits and credits balanced? In that case, we would have added one line to our configuration, like so:

1.  <bean id="AccountingDocument-RouteDocument-DefaultValidation" class="CompositeValidation" scope="prototype" abstract="true">
2.    <property name="validations">
3.      <list>
4.        <bean parent="AccountingDocument-requiredAccountingLinesCountValidation" scope="prototype">
5.          <property name="accountingLineGroupName" value="source" />
6.          <property name="minimumNumber" value="1" />
7.          <property name="parameterProperties">
8.            <list>
9.              <bean parent="accountingDocumentFieldConversion" />
10.           </list>
11.         </property>
12.         <property name="quitOnFail" value="true" />
13.       </bean>
.
.
.
31. </bean>

Line 15 specifies the property "quitOnFail". If the CompositeValidation has a Validation that has failed and which has its quitOnFail property set to true, then it quits right there, and does not perform the rest of the validations in the list.

Finally, note CompositeValidation can have any validation listed within its validation property, including other CompositeValidations any level deep of nesting needed.

Easier adding validations together with hutches

Sometimes, a number of homogeneous validations are preformed at once. For instance, whenever an accounting line is added, updated, or reviewed on a document, there's a number of "value allowed" validations which check if certain fields on the accounting line have values that aren't allowed by the specific document. Since there are several similar validations, they were added to a "hutch" class, org.kuali.kfs.sys.document.validation.impl.AccountingLineValuesAllowedValidationHutch. This class has a couple of unique features. First of all, each validation within the hutch is given a name - a hole for the validation to live in (which is why it got called a "hutch"). Secondly, the hutch only takes in the parameter property names for the properties on the event to populate. This is because the validations are considered to be heterogeneous - that is, it expects all the targetValidationProperties to be the same for all the validations in the hutch. This class provided some convenience in writing these validations, since it allows a mechanism sort of like overriding methods to work. The curious should take a look.

Validating every item in a collection with CollectionValidations 

What about those cases when we want to validate every item in a collection on a document the same way? CompositeValidations are no good there, because they loop through a list of Validations - not a list of items on a document. But...no worries. Another Validation - org.kuali.kfs.sys.document.validation.impl.CollectionValidation - comes to the rescue.

Let's take an example from a new source: org/kuali/kfs/fp/document/validation/configuration/InternalBillingValidation.xml (and note where our document-for-document configurations go...more on that later):

1.  <bean id="InternalBilling-routeDocumentValidation-parentBean" class="CompositeValidation" scope="prototype" abstract="true">
2.    <property name="validations">
3.      <list>
4.        <bean parent="AccountingDocument-RouteDocument-DefaultValidation" scope="prototype" />
5.        <bean class="org.kuali.kfs.validation.CollectionValidation" scope="prototype">
6.          <property name="collectionProperty" value="document.items" />
7.          <property name="validations">
8.            <list>
9.              <bean parent="AccountingDocument-businessObjectDataDictionaryValidation" scope="prototype">
10.               <property name="parameterProperties">
11.                 <list>
12.                   <bean parent="validationFieldConversion">
13.                     <property name="sourceEventProperty" value="iterationSubject" />
14.                     <property name="targetValidationProperty" value="businessObjectForValidation" />
15.                   </bean>
16.                 </list>
17.               </property>
18.             </bean>
19.           </list>
20.         </property>
21.       </bean>
22.     </list>
23.   </property>
24. </bean>

Here we see lots of interesting things. First, we've got a specialized composite bean to handle the validations for Internal Billing's route document event. This event actually uses all of the default validations declared above, and so on line 4, we declare a bean that has the default validation as a parent. Then, on line 5, we add a validation - a CollectionValidation.

As we can see in lines 7 through 20, CollectionValidation acts like CompositeValidation, in that it has a list of Validation classes to validate through (CollectionValidation actually extends CompositeValidation, so these rules work precisely the same as they do in CompositeValidation). On line 6, we have an extra property though: collectionProperty. This is the property of the event to iterate over and apply all the sub-validations to, in this case the List of Items on the InternalBillingDocument.

How do we declare the property conversions for this, though? As CollectionValidation iterates through the collection, it sets a property on the passed in event named iterationSubject. When we need the current subject of the iteration over the collection to send to the validation on line 13, we specify the sourceEventProperty named "iterationSubject".

Note, though, that iterationSubject is defined only within the scope of its nearest CollectionValidation. Obviously, this means that iterationSubject will be null outside the CollectionValidation. It also means that if a CollectionValidation is nested within a parent CollectionValidation, the inner CollectonValidation has access only to the iterationSubject of its own iteration - not the iterationSubject of its parent.

Running mutually exclusive validations with BranchingValidations 

There's one last specialized validation class - org.kuali.kfs.sys.document.validation.impl.BranchingValidation. This is for documents where, based on the state of the document, mutually exclusive validations must take place. A classic case is the DisbursementVoucher, where, based on whether the Payee is a Vendor or an Employee, two different sets of Validations needs to be performed. There are also several examples of BranchingValidations in Accounts Receivable's Customer Invoice document and several Purchasing/Accounts Payable documents.

org.kuali.kfs.sys.document.validation.impl.BranchingValidation has one abstract method - protected abstract String determineBranch(AttributedDocumentEvent event). Unlike CompositeValidation and CollectionValidation - neither of which should ever need to be extended - to get a BranchingValidation to work, you need to extend the class and override the determineBranch method. This method returns the name of a branch.

BranchingValidation also holds a Map property called "branchMap". This Map associates String names with a Validation bean (and that Validation bean can be, of course, a CompositeValidation). When a BranchingValidation is validated, it calls determineBranch. If determineBranch returns a null or empty String, the validation simply returns true. In any other case, the String that the method returns is used to lookup a Validation on the map, and then that Validation is validated.

Associating events with validations 

Validations have been created to validate against the events occurring to our document and we've got the Spring configuration ready. How do we get our document to trigger those Validations when certain events occur? To do that, there's four easy steps:

  1. Create a Spring configuration file for each document, this one only for validations
  2. Add that configuration to the main Spring file for the module the document lives in
  3. Set up the validation to event map in the document's data dictionary
  4. Use the right rules class in the document's data dictionary

Let's look at each step in detail.

Create a Spring configuration file for each document, this one only for validations

As you can no doubt imagine the code and configuration needs for validations can get large very quickly. To keep things manageable, there's a number of best practices about where that code and configuration goes.

First, all the code for Validation belongs in the package org.kuali.kfs.{your module}.document.validation.impl We've tried to set up the practice that if a Validation exists only for one document, it will be named "{DocumentName}{WhatTheRuletteChecks}Validation". Beans are correspondingly named "{DocumentName}-{whatTheRuletteChecks}Validation" and we use the parentBean pattern pioneered by the Spring configuration for data dictionaries.

The Spring configuration for one document's validations can run up to several hundred lines, and therefore, we set up the configuration in its own Spring definition file, which exists in the directory org/kuali/kfs/{your module}/document/validation/configuration. This means that the Spring configuration file for the module as a whole tends to stay pretty clear. As a best practice, the module has a configuration where all the parent beans are created, and then each document's configuration simply tells what validations should be run for each event.

Add that configuration to the main Spring file for your module

Of course, our main Spring file still needs to have a way to access these beans. How does that work? Well, as we can see from this example taken from spring-fp.xml, it's pretty easy:

1.  <!-- validations -->
2.  <import resource="document/validation/configuration/FinancialProcessingValidators.xml" />
3.  <import resource="document/validation/configuration/InternalBillingValidation.xml" />
4.  <import resource="document/validation/configuration/BudgetAdjustmentValidation.xml" />
5.  <import resource="document/validation/configuration/TransferOfFundsValidation.xml" />
6.  <import resource="document/validation/configuration/AuxiliaryVoucherValidation.xml" />
7.  <import resource="document/validation/configuration/CashReceiptValidation.xml" />

Each configuration file is imported as a resource into the main Spring file. Easy, but sadly, also easy to forget....

Set up the Validation event map

We still want to associate validations with events; for instance, we want the InternalBilling-routeDocumentValidation to run when an InternalBilling document has a route event triggered against it. That's easily set up in the data dictionary. Let's check out org/kuali/kfs/fp/document/datadictionary/InternalBillingDocument.xml:

1.  <bean id="InternalBillingDocument-validations" parent="InternalBillingDocument-validations-parentBean" />
  
2.  <bean id="InternalBillingDocument-validations-parentBean" class="org.springframework.beans.factory.config.MapFactoryBean">
3.    <property name="sourceMap">
4.      <map>
5.        <entry>
6.          <key><value>org.kuali.kfs.sys.document.validation.event.AttributedRouteDocumentEvent</value></key>
7.          <value>InternalBilling-routeDocumentValidation</value>
8.        </entry>
9.        <entry>
10.         <key><value>org.kuali.kfs.sys.document.validation.event.AttributedApproveDocumentEvent</value></key>
11.         <value>InternalBilling-approveDocumentValidation</value>
12.       </entry>
13.       <entry>
14.         <key><value>org.kuali.kfs.sys.document.validation.event.AttributedBlanketApproveDocumentEvent</value></key>
15.         <value>InternalBilling-blanketApproveDocumentValidation</value>
16.       </entry>
17.       <entry>
18.         <key><value>org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent</value></key>
19.         <value>InternalBilling-addAccountingLineValidation</value>
20.       </entry>
21.       <entry>
22.         <key><value>org.kuali.kfs.sys.document.validation.event.DeleteAccountingLineEvent</value></key>
23.         <value>InternalBilling-deleteAccountingLineValidation</value>
24.       </entry>
25.       <entry>
26.         <key><value>org.kuali.kfs.sys.document.validation.event.UpdateAccountingLineEvent</value></key>
27.         <value>InternalBilling-updateAccountingLineValidation</value>
28.       </entry>
29.       <entry>
30.         <key><value>org.kuali.kfs.sys.document.validation.event.ReviewAccountingLineEvent</value></key>
31.         <value>InternalBilling-reviewAccountingLineValidation</value>
32.       </entry>
33.     </map>
34.   </property>
35. </bean>

Here, we simply associate a class of an event with the name of the bean that we want validated when the event occurs. When an AttributedRouteDocumentEvent occurs to an Internal Billing document, the bean InternalBilling-routeDocumentValidation will handle the validation.

Note that we're not using <ref> declarations to access the beans, just names. This is because the data dictionary Spring context is separate from the Spring context for the rest of the application (ie, where we set up all our validation beans). Therefore, we simply specify the name; the rules engine will get the actual Spring bean for the Validation from the main application SpringContext.

Second, note that we're using "AttributedRouteDocumentEvent" - not org.kuali.kfs.sys.document.validation.event.AttributedRouteDocumentEvent, which is what really gets thrown by KNS. What's up with that? Well, RouteDocumentEvent demands a different interface than the simple interface needed by our rules class (which we'll get to presently). While accounting document centric events in KFS will eventually just expect to call their rule against the AccountingRuleEngineRuleBase class, the KNS ones will never move. This has two interesting side effects. First: we've got to map our validations to the wrapped event, ie AttributedRouteDocumentEvent. Also, all the validation that KNS does for documents will always occur anyway - we can't prevent it from happening. That's typically a good thing, though - it would be really atrocious if we had to redeclare all of KNS's document validations within our configuration too!

We do not need to specify a Validation to respond to every event that could happen on a document. Several documents, for instance, do no Validations when a SaveDocumentEvent occurs - and therefore, they do not have an associated Validation. When the document is being validated and the rules engine can't find the event in the specified event map, then it assumes that validation passes for that and returns true.

Finally, there's two different validations for ApproveDocumentEvent and BlanketApproveDocumentEvent. However, as we all know, BlanketApproveDocumentEvent extends ApproveDocumentEvent - doesn't that mean that ApproveDocumentEvent validations will get automatically approved whenever BlanketApproveDocumentEvents get thrown? Not with the Accouting Documents validation. The rules engine looks for precisely the event class in the map which it is validating for; it doesn't try to figure out inheritance rules for those classes. Therefore, if a validation needs to run for both ApproveDocumentEvents and BlanketApproveDocumentEvents, it must be configured for both events.

One final line of code - we need to tell our document's data dictionary definition that this is its rule map. Once again, we consult org/kuali/kfs/fp/datadictionary/docs/InternalBillingDocument.xml:

1.  <bean id="InternalBillingDocument-parentBean" abstract="true" parent="TransactionalDocumentEntry">
2.    <property name="label" value="Internal Billing" />
3.    <property name="documentTypeName" value="InternalBillingDocument" />
4.    <property name="documentClass" value="org.kuali.module.financial.document.InternalBillingDocument" />
5.    <property name="businessRulesClass" value="org.kuali.kfs.sys.document.validation.impl.AccountingRuleEngineRuleBase" />    
.
.
.
6.    <property name="validationMap" ref="InternalBillingDocument-validations" />
7.  </bean>

On line 6, you'll see that we set the validationMap property on the TransactionalDocumentEntry for the InternalBillingDocument-parent to be the map that we just declared.

Use the right rules class

We actually already did that: check out line 5 of the definition above. Our rules class for every document using this engine will be org.kuali.kfs.sys.document.validation.impl.AccountingRuleEngineRuleBase.