The data dictionary is the main repository for metadata storage and provides the glue to combining classes related to a single piece of functionality. The data dictionary is specified in XML and allows for quick changes to be made to functionality. The Data Dictionary files use the Spring Framework for configuration so the notation and parsing operation will match that of the files that define the module configurers.
The contents of the data dictionary are defined by two sets of vocabularies; the ‘business object’ and the ‘document’ data.
Business Object Data Dictionary entries provide the KNS framework extra metadata about a business object which is not provided by the persistence mapping or the class itself.
The business object data dictionary contains information about:
Descriptive labels for each attribute in the business object (data dictionary terminology uses the term “attribute” to refer to fields with getter/setter methods).
Metadata about each attribute
How input fields on HTML pages should be rendered for an attribute (e.g. textbox, drop down, etc.)
The data elements from the business object that are shown to users on the KNS Inquiry page
The data elements of the business object that can be used as criteria or shown as result data in the KNS Lookup for the business object
The business object data dictionary does not contain information about:
Which BO does a table correspond to (responsibility of persistence layer, e.g. OJB)
How fields in the BO correspond to database columns (responsibility of persistence layer, e.g. OJB)
The orientation of various fields on user interface screens
One thing to note is the use of ‘abstract’ parent beans within the Rice files. These are used to facilitate easy overriding of beans from Rice in a client application or a customized Rice standalone server installation. Take the following example where the “RealBean” may be defined within Rice:
<bean id=”RealBean” parent=”RealBean-parent” /> <bean id=”RealBean-parent” abstract=”true” />
Client applications overriding this bean definition should always retain the id “RealBean”. This allows for any developer working with overriding data dictionary files to easily define an override using the following parent bean structure:
<bean id=”RealBean” parent=”RealBean-client-parent” /> <bean id=”RealBean-client-parent” abstract=”true” parent=”RealBean-parent” > <!—- any client overrides go here --> </bean>
The setup above will take any configuration from the Rice defined “RealBean-parent” and allow the client developer to override individual properties inside the bean. Then when anything inside Rice or the client application references the data dictionary bean “RealBean” they will get the Rice defined values unless they were overridden by client application developers. See the Spring Framework documentation for more examples of this.
For the sake of this documentation, the abstract parent bean structure will be mostly ignored but its operation is consistent throughout all data dictionary files.
A sample Data Dictionary file to show typical organization of various beans that may be defined:
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="Account" parent="Account-parentBean"/> <bean id="Account-parentBean" abstract="true" parent="BusinessObjectEntry"> <property name="businessObjectClass" value="org.kuali.kfs.coa.businessobject.Account"/> <property name="inquiryDefinition" ref="Account-inquiryDefinition"/> <property name="lookupDefinition" ref="Account-lookupDefinition"/> <property name="titleAttribute" value="accountNumber"/> <property name="objectLabel" value="Account"/> <!-- Attribute definition --> <property name="attributes"> <list> <!-- list goes here --> </list> </property> <!-- Collections --> <property name="collections"> <list> <!-- list goes here --> </list> </property> <!-- Relationships --> <property name="relationships"> <list> <!-- list goes here --> </list> </property> <!-- Inactivation blocking definitions --> <property name="inactivationBlockingDefinitions"> <list> <!-- list goes here --> </list> </property> </bean> <bean id="Account-inquiryDefinition" parent="Account-inquiryDefinition-parentBean"/> <!-- Definition of ‘Account-inquiryDefinition-parentBean’ bean goes here --> <bean id="Account-lookupDefinition" parent="Account-lookupDefinition-parentBean"/> <!—Definition of ‘Account-lookupDefinition-parentBean’ bean goes here --> </beans>
A more specific Rice example might be the CampusImpl object (whose business object data dictionary file is Campus.xml). Here is the main bean definition from that file:
<bean id="Campus-parentBean" abstract="true" parent="BusinessObjectEntry"> <property name="businessObjectClass" value="org.kuali.rice.kns.bo.CampusImpl"/> <property name="inquiryDefinition"> <ref bean="Campus-inquiryDefinition"/> </property> <property name="lookupDefinition"> <ref bean="Campus-lookupDefinition"/> </property> <property name="titleAttribute" value="campusCode"/> <property name="objectLabel" value="Campus"/> <property name="attributes"> <list> <ref bean="Campus-campusCode"/> <ref bean="Campus-campusName"/> <ref bean="Campus-campusShortName"/> <ref bean="Campus-campusTypeCode"/> <ref bean="Campus-versionNumber"/> </list> </property> </bean>
One of the main properties required is the businessObjectClass which defines the java implementation class that this business object data dictionary file will be used for.
The inquiryDefinition and the lookupDefinition will be covered later in this document but for now simply note that the property is using a <ref> tag to point to a bean id that exists elsewhere in this file.
The titleAttribute property defines the attribute of the business object that is the primary key. This is typically used to define which attribute can be used to display the inquiry page.
The objectLabel property is the label that will be used for all general business object references including where the system has collections of the business object.
Attribute definitions are used to provide metadata about the attributes (i.e. fields) of a business object. The following is a sampling of attribute definitions from the CampusImpl business object data dictionary file:
<bean id="Campus-campusCode-parentBean" abstract="true" parent="AttributeDefinition"> <property name="forceUppercase" value="true"/> <property name="shortLabel" value="Campus Code"/> <property name="maxLength" value="2"/> <property name="validationPattern"> <bean parent="AlphaNumericValidationPattern"/> </property> <property name="required" value="true"/> <property name="control"> <bean parent="TextControlDefinition" p:size="2"/> </property> <property name="summary" value="Campus Code"/> <property name="name" value="campusCode"/> <property name="label" value="Campus Code"/> <property name="description" value="The code uniquely identifying a particular campus."/> </bean> <bean id="Campus-campusTypeCode-parentBean" abstract="true" parent="AttributeDefinition"> <property name="forceUppercase" value="true"/> <property name="shortLabel" value="Type"/> <property name="maxLength" value="2"/> <property name="validationPattern"> <bean parent="AlphaNumericValidationPattern"/> </property> <property name="required" value="true"/> <property name="control"> <bean parent="SelectControlDefinition" p:valuesFinderClass="org.kuali.rice.kns.keyvalues.CampusTypeValuesFinder" p:includeKeyInLabel="false"/> </property> <property name="summary" value="Campus Type Code"/> <property name="name" value="campusTypeCode"/> <property name="label" value="Campus Type Code"/> <property name="description" value="The code identifying type of campus."/> </bean>
In client applications, it is common that several business objects share a field representing the same type of data. For example, a country’s postal code may occur in many different tables. In these circumstances, the use of a parent bean reference (parent="Country-postalCountryCode”) definition allows the reuse of parts of a standard definition from the "master" business object. For instance, the StateImpl business object (business object data dictionary file State.xml) references the postalCountryCode property of the CountryImpl (business object data dictionary file Country.xml). Because the postalCountryCode fields in StateImpl and CountryImpl are identical, a simple attribute definition bean in the Business Object data dictionary file (State.xml) can be used:
<bean id="State-postalCountryCode" parent="Country-postalCountryCode-parentBean"/>
The definition of the Country-postalCountryCode-parentBean bean is seen inside the Country.xml file (for the CountryImpl business object):
<bean id="Country-postalCountryCode-parentBean" abstract="true" parent="AttributeDefinition"> <property name="name" value="postalCountryCode"/> <property name="forceUppercase" value="true"/> <property name="label" value="Country Code"/> <property name="shortLabel" value="Country Code"/> <property name="maxLength" value="2"/> <property name="validationPattern"> <bean parent="AlphaNumericValidationPattern"/> </property> <property name="required" value="true"/> <property name="control"> <bean parent="TextControlDefinition" p:size="2"/> </property> <property name="summary" value="Postal Country Code"/> <property name="description" value="The code uniquely identify a country."/> </bean>
This type of definition (defining the attribute definition once and reusing the bean as a parent bean) can be used inside common files as well. Rice has an AttributeReferenceDummy.xml business object data dictionary file as well as a java object AttributeReferenceDummy.java file. This file’s sole purpose is to place commonly defined attributes such as versionNumber (which is common across all business objects) in a central location so that other business object attribute definitions can use them as parent beans. Here is how the Campus business object uses the version number attribute:
<bean id="Campus-versionNumber-parentBean" abstract="true" parent="AttributeReferenceDummy-versionNumber">
All business object data dictionary files need to have the version number field bean defined. This will verify that the UI will have the version number as a hidden field.
A lookup definition contains a property called lookupFields which is made up of a list of FieldDefinitions. These specify the fields that will be displayed on a lookup form for that business object. A typical lookupField (shown here with the parent property for context) in the Spring configuration for a Business Object will look like this:
<property name="lookupFields"> <list> ... <bean parent="FieldDefinition" p:attributeName="campusCode"/> ... </list> </property>
You can set a global default for that lookup field using the defaultValue property:
<bean parent="FieldDefinition" p:attributeName="campusCode" p:defaultValue="BL"/>
The effect of this is that every time the lookup for this Business Object is rendered, the campusCode text input will have "BL" in it.
A quickfinder is a button that is rendered next to a lookup field which takes you to a lookup for a related Business Object which that field references, which in the case of this example would be to a Campus Business Object.
If a lookup field will have a quickfinder button on it due to a BO relationship, you may wish to set default values for certain fields on that related Business Object's lookup form, but only when the quickfinder from this Business Object is used.
<bean parent="FieldDefinition" p:attributeName="campusCode" p:quickfinderParameterString="campusTypeCode=P,active=Y" />
The effect of this is different than the defaultValue in that the defaults apply not to the lookup form Business Object that we are currently defining lookupFields for, rather for specific fields in the related Business Object that this lookupField (campusCode) references – but only when accessed through this quickfinder on our parent BO's lookup form.
This is perhaps better explained through a simple example with two BOs that have a relationship, Building and Campus. Here is the LookupDefinition for Building:
<bean id="Building-lookupDefinition-parentBean" abstract="true" parent="LookupDefinition" p:title="Building Lookup"> ... <property name="lookupFields"> <list> ... <bean parent="FieldDefinition" p:attributeName="campusCode" p:quickfinderParameterString="campusTypeCode=P,active=Y" defaultValue="BL"/> ... </list> </property> ... </bean>
The defaultValue is a global default, so every time you view the Building BO's lookup it will have "BL" in the campusCode input.
The quickfinderParameterString is much more localized, so if you go directly to the Campus BO's lookup it will have no effect. However, if you go to the Building BO's lookup and click the quickfinder button next to its campusCode input, the Campus BO's lookup it will have a default of "P" in the campusTypeCode input, and a default of "Y" in the active input.
There is a related property for FieldDefinition that also applies to lookups, the quickfinderParameterStringBuilderClass. This lets you specify a class (which must implement the org.kuali.rice.kns.lookup.valueFinder.ValueFinder interface) which will dynamically construct a quickfinderParameterString each time a lookup is rendered. This might be useful if e.g. you wanted to populate a field in the related BO's lookup with the current date and time when it is accessed through the quickfinder.
It is not valid to have both the quickfinderParameterString and the quickfinderParameterStringBuilderClass defined on a single FieldDefinition, and you will get an exception during Data Dictionary validation if you do so.
Support exists in the lookup framework for totaling the lookup results. If the ‘total’ property is set to true on one or more FieldDefinition within the resultFields, the total line will be rendered and totals displayed for each field indicated.
Example:
<property name="resultFields" > <list> <bean parent="FieldDefinition" p:attributeName="kemid" /> <bean parent="FieldDefinition" p:attributeName="kemidObj.shortTitle" /> <bean parent="FieldDefinition" p:attributeName="kemidObj.purposeCode" /> <bean parent="FieldDefinition" p:attributeName="availableIncomeCash" p:total="true" /> <bean parent="FieldDefinition" p:attributeName="availablePrincipalCash" p:total="true" /> <bean parent="FieldDefinition" p:attributeName="availableTotalCash" p:total="true" /> <bean parent="FieldDefinition" p:attributeName="kemidObj.close" /> </list> </property>
An additional row will be added to the lookup result table with the totals for each of these columns indicated. The label for the total row will display in the first lookup column. By default this label is set to 'TOTALS' and can be changed in KR-ApplicationResources.properties.
The total line will not be displayed for the column if the column values are masked.
One limitation of the totaling functionality is it will not work with a column that has inquiry URLs. This is because of the need to have a numeric value to sum on and for fields with an inquiry the URL is put into the tag value along with the actual cell value.
In certain cases the search and clear buttons for a lookup are not needed. Therefore these buttons can be disabled in one of two ways.
The first way is to disable the buttons through the data dictionary. This is done by setting the property disableSearchButtons to true in the data dictionary lookup definition:
<bean id="CustomerProfile-lookupDefinition" parent="CustomerProfile-lookupDefinition-parentBean"/> <bean id="CustomerProfile-lookupDefinition-parentBean" abstract="true" parent="LookupDefinition"> <property name="title" value="Customer Profile Lookup"/> <property name="disableSearchButtons" value="true"/>
The second way is to disable the buttons for a particular instance of a lookup by passing disableSearchButtons=true as a request URL parameter:
http://localhost:8080/kr-dev/lookup.do?disableSearchButtons=true&more parms ...
Note in this scenario other calls to the lookup without this parameter will have the search buttons rendered.
There are instances when an institution would choose to add custom attributes to existing data dictionary definitions in a client application (such as KFS or KC). The lookup and result fields representing these custom attributes can be arranged as desired using the DataDictionaryBeanOverride. In the example below, we wish to add a custom attribute named Campus Code to KFS’s existing Account bean.
<beans> ... <bean id=”Account” parent=”Account-parentBean"> <property name="attributes"> <list merge=”true”> <!-- list goes here --> <bean id=”Account.campusCode” parent=”Account-CampusCode” p:name=”Account.campusCode” /> ... </list> </property> </bean> ... </beans>
Once the custom attribute is defined, we create a bean that takes KFS’s Account-lookupDefinition bean and modifies it such that Campus Code is displayed right after the Sub-Fund Group Code attribute in the Account lookup screen and search results.
<beans> ... <bean id=”Account-lookupDefinition-override” parent=”DataDictionaryBeanOverride"> <property name="beanName" value=”Account-lookupDefinition” /> <property name=”fieldOverrides”> <list> <!—- Place Campus Code after Account Sub-Fund Group Code in the lookup --> <bean parent=”FieldOverrideForListElementInsert”> <property name=”propertyName” value=”lookupFields” /> <property name=”propertyNameForElementCompare” value=”attributeName” /> <property name=”element”> <bean parent=”FieldDefinition” p:attributeName=”subFundGroupCode” /> </property> <property name=”insertAfter”> <list> <bean parent=”FieldDefinition” p:attributeName=”Account.campusCode” /> </list> </property> </bean> <!—- Place Campus Code after Account Sub-Fund Group Code in the search results --> <bean parent=”FieldOverrideForListElementInsert”> <property name=”propertyName” value=”resultFields” /> <property name=”propertyNameForElementCompare” value=”attributeName” /> <property name=”element”> <bean parent=”FieldDefinition” p:attributeName=”subFundGroupCode” /> </property> <property name=”insertAfter”> <list> <bean parent=”FieldDefinition” p:attributeName=”Account.campusCode” /> </list> </property> </bean> </list> </property> </bean> ... </beans>
There are instances when an institution would choose to add custom attributes to existing data dictionary definitions in a client application (such as KFS or KC). The fields representing these custom attributes can be arranged on the inquiry screen as desired using the DataDictionaryBeanOverride. In the example below, we wish to add a custom attribute named Campus Code to KFS’s existing Account bean.
<beans> ... <bean id=”Account” parent=”Account-parentBean"> <property name="attributes"> <list merge=”true”> <!-- list goes here --> <bean id=”Account.campusCode” parent=”Account-CampusCode” p:name=”Account.campusCode” /> ... </list> </property> </bean> ... </beans>
Once the custom attribute is defined, we create a bean that takes KFS’s Account-inquiryDefinition bean and modifies it such that Campus Code is displayed right after the Sub-Fund Group Code attribute in the Account inquiry screen.
<beans> ... <bean id=”Account-inquiryDefinition-override” parent=”DataDictionaryBeanOverride"> <property name="beanName" value=”Account-inquiryDefinition” /> <property name=”fieldOverrides”> <list> <!—- Place Campus Code after Account Sub-Fund Group Code in the Account Details section (inquirySections[0]) --> <bean parent=”FieldOverrideForListElementInsert”> <property name=”propertyName” value=”inquirySections[0].inquiryFields” /> <property name=”propertyNameForElementCompare” value=”attributeName” /> <property name=”element”> <bean parent=”FieldDefinition” p:attributeName=”subFundGroupCode” /> </property> <property name=”insertAfter”> <list> <bean parent=”FieldDefinition” p:attributeName=”Account.campusCode” /> </list> </property> </bean> </list> </property> </bean> ... </beans>
There are two different document types in KNS:
Maintenance Documents
Maintenance Documents create, update, copy, or inactivate either a single business object or a collection of business objects. They are used to perform standard maintenance on data.
Transactional Documents
Transactional Documents represent an action that will occur in the system. They are treated as one-shot documents and need not be edited and modified several times because of their approach in performing an action.
Table 5.2. Comparison of Maintenance and Transactional Documents
Transactional Documents | Maintenance Documents | |
---|---|---|
SQL Table(s) | yes | yes |
OJB Mapping(s) - repository.xml | yes | yes |
Business Object(s) | yes | yes |
Data Dictionary File(s)(XML) | Transactional Document DD File |
Maintenance Document DD File Business Object DD File (discussed earlier) |
Each type of dictionary defines properties such as authorizations, rules and workflow document types.
The following examples all follow the same structure with respect to the use of ‘abstract’ parent beans for Data Dictionary beans. A detailed description of their use and why Kuali uses this type of implementation can be found in the beginning of the ‘Business Object Data Dictionary’ section.
In general, documents have metadata associated with them, and the metadata for maintenance documents exists in the document's data dictionary configuration. The data dictionary can do practically everything for a maintenance document: it declares the user interface for the form, ties rules and document authorizers to the document as well as the document's workflow document type.
Below is an example of a Maintenance Document Data Dictionary file from the KNS module itself. It is for the Parameter object used within the KNS. The path (or package) org/kuali/rice/kns/document/datadictionary/ is where the ParameterMaintenanceDocument can be found in Rice if below is difficult to view.
<bean id="ParameterMaintenanceDocument" parent="ParameterMaintenanceDocument-parentBean"/> <bean id="ParameterMaintenanceDocument-parentBean" abstract="true" parent="MaintenanceDocumentEntry"> <property name="businessObjectClass" value="org.kuali.rice.kns.bo.Parameter"/> <property name="maintainableClass" value="org.kuali.rice.kns.document.ParameterMaintainable"/> <property name="maintainableSections"> <list> <ref bean="ParameterMaintenanceDocument-EditParameter"/> </list> </property> <property name="defaultExistenceChecks"> <list> <bean parent="ReferenceDefinition" p:attributeName="parameterNamespace" p:attributeToHighlightOnFail="parameterNamespaceCode"/> <bean parent="ReferenceDefinition" p:attributeName="parameterType" p:attributeToHighlightOnFail="parameterTypeCode"/> </list> </property> <property name="lockingKeys"> <list> <value>parameterNamespaceCode</value> <value>parameterDetailTypeCode</value> <value>parameterApplicationNamespaceCode</value> <value>parameterName</value> </list> </property> <property name="documentTypeName" value="ParameterMaintenanceDocument"/> <property name="businessRulesClass" value="org.kuali.rice.kns.rules.ParameterRule"/> <property name="documentAuthorizerClass" value="org.kuali.rice.kns.document.authorization.MaintenanceDocumentAuthorizerBase"/> <property name="workflowProperties"> <ref bean="ParameterMaintenanceDocument-workflowProperties"/> </property> </bean> <!-- Maintenance Section Definitions --> <bean id="ParameterMaintenanceDocument-EditParameter" parent="ParameterMaintenanceDocument-EditParameter-parentBean"/> <bean id="ParameterMaintenanceDocument-EditParameter-parentBean" abstract="true" parent="MaintainableSectionDefinition"> <property name="maintainableItems"> <list> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterNamespaceCode"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterDetailTypeCode"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterApplicationNamespaceCode" p:defaultValue="KUALI"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterName"/> <bean parent="MaintainableFieldDefinition" p:required="false" p:name="parameterValue"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterDescription"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterTypeCode"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterConstraintCode"/> </list> </property> <property name="id" value="Edit Parameter"/> <property name="title" value="Edit Parameter"/> </bean> <!-- Exported Workflow Properties --> <bean id="ParameterMaintenanceDocument-workflowProperties" parent="ParameterMaintenanceDocument-workflowProperties-parentBean"/> <bean id="ParameterMaintenanceDocument-workflowProperties-parentBean" abstract="true" parent="WorkflowProperties"> <property name="workflowPropertyGroups"> <list> <bean parent="WorkflowPropertyGroup"> <property name="workflowProperties"> <list> <bean parent="WorkflowProperty" p:path="oldMaintainableObject.businessObject"/> <bean parent="WorkflowProperty" p:path="newMaintainableObject.businessObject"/> </list> </property> </bean> </list> </property> </bean>
The first bean defined for the ParameterMaintenanceDocument data dictionary file is the main definition bean “ParameterMaintenanceDocument-parentBean”. This bean uses the parent bean “MaintenanceDocumentEntry”. This is how this particular business object is defined specifically as a Maintenance Document. Inside the “ParameterMaintenanceDocument-parentBean” bean we see several properties being set:
<property name="businessObjectClass" value="org.kuali.rice.kns.bo.Parameter"/> <property name="maintainableClass" value="org.kuali.rice.kns.document.ParameterMaintainable"/>
First and foremost the Maintenance Document Data Dictionary file should define the business object that will be maintained by this particular document using the businessObjectClass property. In this example the fully qualified business object class is kuali.rice.kns.bo.Parameter.
The Maintenance Documents also need a maintainable class. This is defined using the maintainableClass property and in our Parameter business object example the custom class being used is org.kuali.rice.kns.document.ParameterMaintainable. If there are no customizations needed for the business object then the default class org.kuali.rice.kns.maintenance.KualiMaintainableImpl should be used. More will be discussed about custom maintainable classes later in this document.
The next maintenance document specific tag is defaultExistenceChecks. Certain document validations are so omnipresent that they can simply be declared - typically validations that certain fields of a document are required. Here are the default existence checks for the ParameterMaintenanceDocument:
<property name="defaultExistenceChecks"> <list> <bean parent="ReferenceDefinition" p:attributeName="parameterNamespace" p:attributeToHighlightOnFail="parameterNamespaceCode"/> </list> </property>
Here we have just one default existence check. Default existence checks verify that the associated business object for the document actually exist. For instance, in the Parameter maintenance document, if a user enters a parameter namespace value that does not exist, the default existence check will display an error message next to the parameterNamespaceCode attribute field after the user attempts to save or submit.
The defaultExistenceCheck tag has a few different ways it can operate. All involve setting a list of beans that use the “ReferenceDefinition” parent bean. This bean is defined in Rice and can be used by any Maintenance Document Data Dictionary file. The properties that may be set for the “ReferenceDefinition” beans vary but the example shows the most common. The attributeName property is set to the KNS attribute name of the business object which must exist for the check to pass. In this case the Namespace object in KNS has a namespaceCode attribute. Likewise the attributeToHighlightOnFail refers to the attribute in the Parameter business object that is used to link to the reference business object. This is the field which will be highlighted on the user interface for the error to display. Of course, for this to work correctly, the foreign keys to the fields must be specified as required. That will come into play in section below about specifying the UI.
Since maintenance documents edit one or more business objects, there is the potential for race conditions. For example, if two business objects were created with the same primary key field and they were both sent into routing at the same time, the first document that is approved to ‘Final’ status in Workflow could potentially be overwritten in the database by the second document when it goes to ‘Final’ status. The KNS attempts to prevent these situations from arising by creating a pessimistic lock on each business object going through workflow as part of a maintenance document. In most cases, it uses the lockingKeys tag of the data dictionary for the maintenance document to create that locking representation. Here's the locking representation configuration for the ParameterMaintenanceDocument:
<property name="lockingKeys"> <list> <value>parameterNamespaceCode</value> <value>parameterDetailTypeCode</value> <value>parameterApplicationNamespaceCode</value> <value>parameterName</value> </list> </property>
Not surprisingly, the attributes listed in the example are also the primary keys for the Parameter business object. The locking keys above simply mean that once a certain Parameter is put into Workflow routing with a certain set of the fields above, another document with the same exact values for all the attributes above will be prevented from being put into Workflow. The fields used in a locking key can be anything, as long as it marks the business object uniquely. It makes sense, then, that most locking keys are simply the primary keys for the business object.
Finally, the largest part of the maintenance document data dictionary: the definition of the UI through the maintenanceSections property. The UI of a maintenance document is made up of one or more maintainable sections. Each section is named, and each section creates a new tab as its visual representation on the web form. Here is the section list property being set on the “ParameterMaintenanceDocument-parentBean” bean (only one section in this document):
<property name="maintainableSections"> <list> <ref bean="ParameterMaintenanceDocument-EditParameter"/> </list> </property>
The list of beans is defined in the main Maintenance Document Entry bean while each ‘Section Definition’ bean is defined below in the file. Here is the ParameterMaintenanceDocument example of the “ParameterMaintenanceDocument-EditParameter” bean definition:
<bean id="ParameterMaintenanceDocument-EditParameter-parentBean" abstract="true" parent="MaintainableSectionDefinition"> <property name="maintainableItems"> <list> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterNamespaceCode"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterDetailTypeCode"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterApplicationNamespaceCode" p:defaultValue="KUALI"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterName"/> <bean parent="MaintainableFieldDefinition" p:required="false" p:name="parameterValue"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterDescription"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterTypeCode"/> <bean parent="MaintainableFieldDefinition" p:required="true" p:name="parameterConstraintCode"/> </list> </property> <property name="id" value="Edit Parameter"/> <property name="title" value="Edit Parameter"/> </bean>
Each maintainable section is defined by using the parent bean “MaintainableSectionDefinition”. These beans, in turn, are made up of several different properties including an id, title, and maintainableItems. The maintainableItems property is a list of maintainable fields. Each maintainable field bean uses the “MaintainableFieldDefinition” bean as its parent bean and lists the attribute that should be shown. That attribute itself has typically been defined in the data dictionary configuration for the business object (see Business Object Data Dictionary Definition below). There is also a required property which can be set to force extra validation, though all validations described in the attributes of the business object will also be checked.
While attributes default to using the definition set up in the data dictionary for a given field, there are a couple of behavior modifications that can be made. One of which appears above in the required property. This can override the default required behavior as defined for the business object on the Business Object Data Dictionary file. Below are demonstrations of how some of the various changes that can be made could potentially be done for the ParameterMaintenanceDocument data dictionary file. For instance, default values for any field can be set by using the defaultValue property, like so:
<bean parent="MaintainableFieldDefinition"> <property name=”name” value="parameterApplicationNamespaceCode"/> <property name=”required” value="true"/> <property name=”defaultValue” value="KUALI"/> </bean>
The example above example sets the default value of the parameterApplicationNamespaceCode attribute to “KUALI”.
Another property that can be used to set a field with a default value in the maintenance document data dictionary maintainableField beans is the defaultValueFinderClass property. This property should be set to a class that implements the interface class org.kuali.rice.kns.lookup.valueFinder.ValueFinder. The interface has one method only: getValue(), which returns a String which will be set into the form in the User Interface. Here is an example (not from the ParameterMaintenanceDocument but from the IdentityManagementGenericPermissionMaintenanceDocument) on how to use the defaultValueFinderClass property:
<bean parent="MaintainableFieldDefinition"> <property name=”name” value="permissionId"/> <property name=”unconditionallyReadOnly” value="true"/> <property name=”required” value="true"/> <property name=”defaultValueFinderClass” value="org.kuali.rice.kim.lookup.valuefinder.NextPermissionIdValuesFinder"/> </bean>
The above example pulls the next available id from a class for one of the Kuali Identity Management documents. This is a very custom behavior for KIM but does highlight just one way the defaultValueFinderClass can be used.
One other large customization that can be made is to modify the way the lookup on a particular field operates. Lookups will be described in detail later in this documentation. Below is a simulated example that does not exist in the Rice code:
<bean parent="MaintainableFieldDefinition"> <property name=”name” value="reconcilerGroup.groupName"/> <property name=”required” value="true"/> <property name=”overrideFieldConversions” value="groupId:cardGroupId,groupName:reconcilerGroup.groupName"/> <property name=”overrideLookupClass” value="org.kuali.rice.kim.bo.impl.GroupImpl"/> </bean>
The overrideLookupClass property will set the business object class of the lookup that should be used. This means in our example above that the lookup for the field “reconcilerGroup.groupName” will use the org.kuali.rice.kim.bo.impl.GroupImpl class lookup. The overrideFieldConversions property is used to translate data attributes from the overrideLookupClass to fields that match the current Business Object class for which the maintenance document data dictionary file is for. These are separated with the colon character and a comma is used to delineate each field translation if more than one is to be listed. In the example above the ‘groupId’ field (which exists on the org.kuali.rice.kim.bo.impl.GroupImpl class) will be set into the ‘cardGroupId’ field (which should exist on the business object class of the current maintainable data dictionary file). In some instances the overrideFieldConversions may not be necessary if the field names are the same on the lookup’s business object class and the data dictionary’s business object class.
For each MaintainableFieldDefinition bean defined in a maintenance document, there are a few fields that can help adjust the User Interface for a KNS client. Here is a sample example:
... 1 <property name="maintainableItems"> 2 <list> 3 <bean parent="MaintainableFieldDefinition" p:name="Code" p:required="true" /> 4 <bean parent="MaintainableFieldDefinition" p:name="ID" p:unconditionallyReadOnly="true" /> 5 <bean parent="MaintainableFieldDefinition" p:name="Name" p:readOnlyAfterAdd="true" /> 6 <bean parent="MaintainableFieldDefinition" p:name="Type" p:lookupReadOnly="true" /> 7 <bean parent="MaintainableFieldDefinition" p:name="linkedJob" p:noLookup="true" /> ...
In the example above on line 4 the field with name value “ID” has a property named unconditionallyReadOnly that is set to “true”. This means the field will be read only and uneditable in the User Interface at all times regardless of document state. This could be helpful when setting a default value that the user entering the document is not allowed to change.
The property readOnlyAfterAdd set to “true” on line 5 for the “Name” field means that once the maintenance document for this business object has been successfully saved and routed through all appropriate approvals, the “Name” field will be read only. This is useful in certain instances when creating a new business object.
The property lookupReadOnly in line 6 is used to change the UI so that a lookup link will be presented for the field but the value that is displayed when returning an object from the lookup is read only. In the example above the “Type” variable will have a lookup (as defined by the Business Object Data Dictionary file… see the Business Object Data Dictionary section for more information) but the displayed value in the UI for “Type” will be uneditable by user entry. It may still be changed by going to the lookup link again.
The noLookup property shown in line 7 for the “linkedJob” field is a way to override the default functionality coming from the Business Object Data Dictionary file. If that DD file has a Lookup control element but the lookup need to be hidden on the Maintenance Document then this attribute allows for that functionality.
Some maintenance documents include collections of business objects. Below is an example from the RoutingRuleMaintenanceDocument data dictionary file from Rice:
<bean id="RoutingRuleMaintenanceDocument-PersonResponsibilities-parentBean" abstract="true" parent="MaintainableSectionDefinition"> <property name="id" value="PersonsMaintenance"/> <property name="title" value="Persons"/> <property name="maintainableItems"> <list> <bean parent="MaintainableCollectionDefinition"> <property name="name" value="personResponsibilities"/> <property name="businessObjectClass" value="org.kuali.rice.kew.rule.PersonRuleResponsibility"/> <property name="summaryTitle" value="Person"/> <property name="summaryFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="principalName"/> <bean parent="MaintainableFieldDefinition" p:name="actionRequestedCd"/> </list> </property> <property name="maintainableFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="principalName" p:required="true"/> <bean parent="MaintainableFieldDefinition" p:name="actionRequestedCd" p:required="true"/> <bean parent="MaintainableFieldDefinition" p:name="priority" p:required="true”/> </list> </property> <property name="duplicateIdentificationFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="principalName"/> <bean parent="MaintainableFieldDefinition" p:name="actionRequestedCd"/> </list> </property> </bean> </list> </property> </bean>
To put a collection into a maintenance section, simply put an instance of a MaintainableCollectionDefinition bean in the list that is set into the maintainableItems property of the maintenance section.
The MaintainableCollectionDefinition bean must have a name property. The name property should match the attribute name of the collection being maintained on the original business object. The businessObjectClass property value specifies the class of the items in the collection.
The maintainableFields property inside the MaintainableCollectionDefinition bean works exactly like the previously described structure of the maintainableFields property inside the MaintainableSectionDefinition bean. The only difference is that the name property of each MaintainableFieldDefinition refers to an attribute of the businessObjectClass that is set on the MaintainableCollectionDefinition bean.
The summaryTitle and summaryFields properties are used for display purposes once a list element is added to the list on the UI screen. The specified data elements will show when the full detail of the collection item is hidden using the ‘hide/show’ button functionality of the KNS. Usually these fields are specific to what uniquely defines the business objects contained within the collection.
The duplicateIdentificationFields property is used to identify specifically the set of fields inside the collection element business object that cannot be duplicated in the list. In this way they act as mini-locks. They will prevent more than one list element with the same set of fields. For instance, in the example above, if a list element already exists with the actionRequestedCd ‘A’ and the principalName ‘john’ then another list element with those same values cannot be added.
There are also a few more advanced type attributes that can be used. Take the above example and the abbreviated alteration below.
... 1 <property name="maintainableItems"> 2 <list> 3 <bean parent="MaintainableCollectionDefinition" > 4 <property name="name" value="personResponsibilities"/> 5 <property name="includeAddLine" value="false"/> 6 <property name="businessObjectClass" value="org.kuali.rice.kew.rule.PersonRuleResponsibility"/> 7 <property name="maintainableFields"> 8 <list> 9 <bean parent="MaintainableFieldDefinition" p:name="newCollectionRecord"/> ...
The property includeAddLine on line 5 above is used to remove the UI element that allows the users to add their own elements to the list. This is helpful in cases where the list of items may be statically generated by code internal to the business object containing the collection.
On line 9 in the example above, the addition of the MaintainableFieldDefinition with the name property value of “newCollectionRecord” is used to tell the maintenance framework that any records currently existing in the collection are permanent - that is, there should not be delete buttons associated with them. However, if the property includeAddLine is set to “false” (or omitted) in the MaintainableCollectionDefinition bean above, new lines could be added to the collection and each of the new lines could be deleted (though lines that had been previously saved and routed appropriately into the collection could not be deleted).
Within the business object frameworks (lookup, inquiry, and maintenance document) an alternate or additional property can be specified to display when a field is read-only. These properties are configured through the data dictionary as follows:
This property specifies an attribute on the business object that should be displayed instead of the field attribute when the view is read-only. The property is available on the FieldDefinition for lookup result fields and inquiries, and on the MaintainbleFieldDefinition for maintenance documents. In the case of lookup result fields and inquiries this attribute will always be displayed since the view is always read-only. For maintenance documents, the field attribute will display when the document is editable, and the alternate attribute will display when the document is read-only.
<bean id="CustomerProfile-lookupDefinition" parent="CustomerProfile-lookupDefinition-parentBean"/> <bean id="CustomerProfile-lookupDefinition-parentBean" abstract="true" parent="LookupDefinition"> <property name="title" value="Customer Profile Lookup"/> <property name="defaultSort"> <bean parent="SortDefinition"> <property name="attributeNames"> <list> <value>id</value> </list> </property> </bean> </property> <property name="lookupFields"> <list> <bean parent="FieldDefinition" p:attributeName="id"/> <bean parent="FieldDefinition" p:attributeName="chartCode"/> <bean parent="FieldDefinition" p:attributeName="unitCode"/> <bean parent="FieldDefinition" p:attributeName="subUnitCode"/> <bean parent="FieldDefinition" p:attributeName="active"/> </list> </property> <property name="resultFields"> <list> <bean parent="FieldDefinition" p:attributeName="id" p:alternateDisplayAttributeName="customerName"/> <bean parent="FieldDefinition" p:attributeName="customerShortName"/> <bean parent="FieldDefinition" p:attributeName="customerDescription"/> <bean parent="FieldDefinition" p:attributeName="contactFullName"/> <bean parent="FieldDefinition" p:attributeNa ="processingEmailAddr"/> <bean parent="FieldDefinition" p:attributeName="defaultPhysicalCampusProcessingCode"/> <bean parent="FieldDefinition" p:attributeName="active"/> <bean parent="FieldDefinition" p:attributeName="defaultChartCode"/> </list> </property> </bean>
In the example above, for the result field 'id' we have specified an alternateDisplayAttibuteName equal to "customerName". When the results are rendered the value of customerName property will be displayed and not the value of the id property. This behavior is the same within an InquiryDefinition.
If specified on a MaintainableFieldDefinition, again the value for the alternateDisplayAttibuteName attribute will be displayed; however any quickfinder or lookup URL will be built using the field property as usual. If the field is editable or hidden, the value of the field property will be used.
This property behaves much like the alternateDisplayAttibuteName, the only difference being the value of the additionalDisplayAttributeName attribute will be appended to the value of the field attribute, using '*-*' as a delimiter.
Neither the alternateDisplayAttibuteName nor additionalDisplayAttributeName need to have an AttributeDefinition defined, however they must have an accessible getter in the business object.
If enabled, fields that have references to a KualiCode class will be found and the corresponding KualiCode name field will be set as the additionalDisplayAttributeName. The object property holding the reference must also prefix the field name. For example, a field name of 'defaultChartCode' and reference name of 'defaultChart' would match, again assuming the type of 'defaultChart' implements KualiCode.
This automatic translation of code fields is turned on by default in the Inquiry framework, but turned off by default in lookups and maintenance documents. It can be configured for each MaintenanceDocumentEntry, LookupDefinition, or InquiryDefinition with the property 'translateCodes'.
For example, in the MaintenanceDocumentEntry:
<bean id="CustomerProfileMaintenanceDocument-parentBean" abstract="true" parent="MaintenanceDocumentEntry">
<property name="businessObjectClass" value="org.kuali.kfs.pdp.businessobject.CustomerProfile"/>
<property name="maintainableClass" value="org.kuali.kfs.pdp.document.datadictionary.CustomerProfileMaintenanceDocumentMaintainableImpl"/>
<property name="maintainableSections">
<list>
<ref bean="CustomerProfileMaintenanceDocument-EditCustomerProfileSection1"/>
<ref bean="CustomerProfileMaintenanceDocument-EditCustomerProfileSection2"/>
<ref bean="CustomerProfileMaintenanceDocument-EditCustomerProfileSection3"/>
<ref bean="CustomerProfileMaintenanceDocument-EditCustomerBank"/>
</list>
</property>
<property name="defaultExistenceChecks">
<list>
<bean parent="ReferenceDefinition" p:attributeName="defaultChart" p:attributeToHighlightOnFail="defaultChartCode"/>
<bean parent="ReferenceDefinition" p:attributeName="defaultAccount" p:attributeToHighlightOnFail="defaultAccountNumber"/>
<bean parent="ReferenceDefinition" p:attributeName="defaultObject" p:attributeToHighlightOnFail="defaultObjectCode"/>
<bean parent="ReferenceDefinition" p:attributeName="defaultProcessingCampus" p:attributeToHighlightOnFail="defaultPhysicalCampusProcessingCode"/>
<bean parent="ReferenceDefinition" p:attributeName="state" p:attributeToHighlightOnFail="stateCode"/>
<bean parent="ReferenceDefinition" p:attributeName="postalCode" p:attributeToHighlightOnFail="zipCode"/>
<bean parent="ReferenceDefinition" p:attributeName="country" p:attributeToHighlightOnFail="countryCode"/>
<bean parent="ReferenceDefinition" p:attributeName="transactionType" p:attributeToHighlightOnFail="achTransactionType"/>
<bean parent="ReferenceDefinition" p:collection="customerBanks" p:attributeName="disbursementType" p:attributeToHighlightOnFail="disbursementTypeCode"/>
<bean parent="ReferenceDefinition" p:collection="customerBanks" p:attributeName="bank" p:attributeToHighlightOnFail="bankCode"/>
</list>
</property>
<property name="lockingKeys">
<list>
<value>chartCode</value>
<value>unitCode</value>
<value>subUnitCode</value>
</list>
</property>
<property name="translateCodes" value="true"/>
If alternateDisplayAttributeName is specified for a field then it will override the code translation (if applicable).
Note the Summarizable interface and SummarizableFormatter class were removed as part of this work. If an application class implemented Summarizable it should be changed to implement the KualiCode interface.
Within the KNS lookup and maintenance frameworks there is support for dynamically altering the read-only, hidden, or required states of a field. This functionality is configured through the data dictionary and java code as follows:
Any conditional logic that is necessary to determine whether a field should be read-only, hidden, or required (and editable) is implemented with java code. For maintenance documents this code is placed in the presentation controller. The following methods are available for this purpose:
public Set<String> getConditionallyReadOnlyPropertyNames(MaintenanceDocument document) public Set<String> getConditionallyRequiredPropertyNames(MaintenanceDocument document) public Set<String> getConditionallyHiddenPropertyNames(BusinessObject businessObject)
Each of these methods returns a Set of field names (prefixing for the maintainable is not necessary). These fields will then take on the state determined by the method. The first two methods take as a parameter the MaintenanceDocument instance which can be used to get the current values for one or more fields. The third method is more general (because it is used for inquires as well) and takes a BusinessObject instance as a parameter. Within the maintenance context this will again be the MaintenanceDocument and can be cast after doing an instanceof check.
Example:
@Override public Set<String> getConditionallyRequiredPropertyNames(MaintenanceDocument document) { Set<String> required = new HashSet<String>(); SubAccount subAccount = (SubAccount) document.getNewMaintainableObject().getBusinessObject(); if (StringUtils.isNotBlank(subAccount.getFinancialReportChartCode()) && subAccount.getFinancialReportChartCode().equals("BL")) { required.add("a21SubAccount.costShareChartOfAccountCode"); required.add("a21SubAccount.costShareSourceAccountNumber"); } return required; }
Only fields that have conditional states need to be considered here. For fields that are always read-only, hidden, or required the corresponding properties on the MaintainableFieldDefinition can be set to true through the data dictionary.
Sections of the maintenance document can also be conditionally set to read-only or hidden by implementing the following methods within the presentation controller:
public Set<String> getConditionallyReadOnlySectionIds( MaintenanceDocument document); public Set<String> getConditionallyHiddenSectionIds(BusinessObject businessObject);
Any authorization restrictions will be applied after this logic by the document authorizer class.
For lookups conditional logic is implemented in the LookupableHelperService. Similar methods exist for determining the read-only, hidden, or required states:
public Set<String> getConditionallyReadOnlyPropertyNames(); public Set<String> getConditionallyRequiredPropertyNames(); public Set<String> getConditionallyHiddenPropertyNames();
Each of these methods returns a Set of field names. Code implemented within these methods has access to the lookupable helper properties. In particular the request parameters can be retrieved using getParameters(), and the current rows using getRows(). The following convenience method is also available for getting a property value from the field:
protected String getCurrentSearchFieldValue(String propertyName)
It is recommended to use this method to get a value for a property as opposed to the request parameters, since the values could be different. This is because the conditional logic is applied at the end of the lookup lifecycle and field values could have been cleared or set to other values by processing code. Therefore basing conditional logic off these values will correctly reflect the values being returned to the search fields.
Example:
@Override public Set<String> getConditionallyHiddenPropertyNames() { Set<String> hiddenPropertyNames = new HashSet<String>(); String employeeId = getCurrentSearchFieldValue(KIMPropertyConstants.Person.EMPLOYEE_ID); if (StringUtils.isNotBlank(employeeId)) { hiddenPropertyNames.add(KFSPropertyConstants.VENDOR_NUMBER); hiddenPropertyNames.add(KFSPropertyConstants.VENDOR_NAME); } return hiddenPropertyNames; }
The second part to implementing conditional logic is indicating which fields should trigger a refresh (page post) when its value changes. The page post will call each of the conditional methods so when the page renders the read-only, required, and hidden attributes are set according to the new field value (Note all field values are available to the conditional methods regardless of which one triggered the refresh). To indicate a field should trigger a refresh, set the triggerOnChange attribute to true on the MaintainableFieldDefinition:
<bean parent="MaintainableFieldDefinition" p:name="financialReportChartCode" p:triggerOnChange="true"/>
For lookups, set the triggerOnChange attribute to true on the lookup FieldDefinition within the lookupFields property:
<property name="lookupFields" > <list> <bean parent="FieldDefinition" p:attributeName="payeeTypeCode"/> <bean parent="FieldDefinition" p:attributeName="taxNumber" /> <bean parent="FieldDefinition" p:attributeName="firstName" /> <bean parent="FieldDefinition" p:attributeName="lastName" /> <bean parent="FieldDefinition" p:attributeName="vendorNumber" p:triggerOnChange="true" /> <bean parent="FieldDefinition" p:attributeName="vendorName" /> <bean parent="FieldDefinition" p:attributeName="employeeId" p:triggerOnChange="true" /> <bean parent="FieldDefinition" p:attributeName="entityId" p:triggerOnChange="true" /> <bean parent="FieldDefinition" p:attributeName="active"/> </list> </property>
There is no limit to the number of trigger fields specified for a maintenance document or lookup.
JavaScript was implemented to set the focus back to the next field in the tab order (from the field that triggered the refresh) when the page refreshes. This will not work correctly if fields are inserted between the field that triggered a refresh and the next tab field (for instance if a field between these two was hidden or read-only, and becomes editable on refresh).