Often times, there are changes in functionality in a client application which functional users want to have control over without an undue technical burden. For instance, a certain set of documents may be associated with a bank; information about a bank is shown on the screen of each of the documents. If more documents are among those to show bank information, functional users would love it if they could just create one maintenance document and that change took effect. By coding with system parameters, such functionality is achievable within the KNS.
A System Parameter is simply a business object which holds text. That text will be used in one of three standard ways: simply as text itself, as an indicator of whether certain logic should be performed or not; or to see if a value from logic falls within a certain set of values. The advantage of using System Parameters is that they are easily changed since a maintenance document already exists as part of the KNS for them.
Parameters are used either for configuration, as described above, or for validation – for instance, if a field on a document can only have one of a certain number of values, and those values need to be changed by a functional user, then a System Parameter would be helpful.
It should be noted that the maintenance of System Parameters is only authorized to those granted the KR-NS Maintain System Parameter KIM permission.
The data from a system parameter can be retrieved through the ParameterService#getParameterValue method, using the parameter’s name to identify the parameter. The parameter’s name has three components: a namespace, a parameter detail type code, and a name field.
The namespace matches a KNS module’s namespace code, typically the namespace code of the module which invokes the parameter. For instance, parameters called within the KNS itself use the base namespace code of “KR-NS”
The name of the parameter should be unique within certain constraints: it must be unique with the namespace, the parameter detail type code, and, as will be covered below, the application namespace. This means that, for instance, if a client application is written with two modules, both modules could create a system parameter with the same name because they would have different namespace codes. Indeed, system parameters within the same module can be named the same thing if they have differing parameter detail type codes.
The parameter detail type code is the most difficult to understand. To understand why, the method signature of ParameterService#getParameterValue must be investigated.
public String getParameterValue(Class<? extends Object> componentClass, String parameterName);
Instead of a String namespaceCode and a String parameterDetailTypeCode, a Class is sent in. That class typically represents the class which will make use of this specific system parameter. From that class is determined both the namespace code and the parameter detail type code.
Finding the namespace code is typically done by looking at the package prefixes in the module configuration. If a class needs to be in a different namespace, it can have the @org.kuali.rice.kns.service.ParameterConstants.NAMESPACE annotation can be used to specify something different.
There is also an @org.kuali.rice.kns.service.ParameterConstants.COMPONENT annotation which can be used to specify a specific parameter detail type code. If that is missing, though, then an algorithm inspects the class to see what parameter detail type code is most appropriate:
If the class represents a transactional class, then the parameter detail type code will the sample name of the class with the trailing expected “Document” removed. For instance, org.kuali.kfs.fp.document.DisbursementVoucherDocument has a parameter detail type code of “DisbursementVoucher”
If the class represents a business object, then the parameter detail type code will be the simple class name. Business object class “org.kuali.kfs.fp.businessobject.PayeeDetail” will have a parameter detail type code of “PayeeDetail”
Any other class will use the simple class name. This particular behavior will eventually be deprecated.
Based on these standards, it should be easy to tell what the parameter detail type code for a given parameter should be.
The parameter’s value is then a simple lookup using the class making the call to ParameterService and the name of the parameter:
final String parameterValue = KNSLocator.getParameterService().getParameterValue(this.getClass(), "SIMPLE_VALUE");
The parameterValue can then be used for whatever purpose the business logic requires.
An indicator parameter’s text is either “Y” or “N”; invoking that parameter as an indicator parameter simply means that the text will be translated to its corresponding boolean value. It is accessed through ParameterService#getIndicatorParameter, which works much as ParameterService#getParameterValue does:
if (KNSLocator.getParameterService().getParameterIndicator(this.getClass(), "EXECUTE_LOGIC_IND")) { // do something... }
Using parameter text is fine if there is only one value in the text. However, very often a parameter may need to be associated with several pieces of text.
For instance, the first example of the System Parameters section talked about having bank information applied to a collection of documents. It seems inefficient to create a bunch of indicator parameters for this. It would be better to create one parameter with a number of document types in the txt.
This is easily done. The standard way is to list the document types in the text, separated by semi-colons as so: FirstDocumentType;SecondDocumentType;ThirdDocumentType
While that could be retrieved via the ParameterService#getParameterValue method and then split, there’s a much better way to examine the value: through the use of a parameter evaluator.
ParameterEvaluators are simply objects which take values from the environment and see if they made the constraints of the parameter. It will do the parsing of the parameter itself and then attempt to match that against an input value:
KNSServiceLocator.getParameterService().getParameterEvaluator(ParameterConstants.NERVOUS_SYSTEM_DOCUMENT, “BANK_DOCUMENT_TYPES”, document.getDocumentType()).evaluationSucceeds();
This looks at the KR-NS / Document / BANK_DOCUMENT_TYPES parameter, splits its semi-colon valued, and then matches document.getDocumentType() against each of the values returned from the split.
The constraint code of the system parameter, mentioned earlier, is invoked at this point. evaluationSucceeds() will return true if document.getDocumentType() is within the values in the parameter and the parameter constraint code is “A” (“allow”). If, on the other hand, the constraint code is “D” (“deny”) and the document type is matched in the parameter’s values, a false will be returned – the document type sent in is denied by the parameter.
(Parameter accessed through getParameterValue() and getIndicatorParameter() should simply set their constraint code to “A”).
System parameters used for validation can add errors if the evaluation fails through the parameter value:
KNSServiceLocator.getParameterService().getParameterEvaluator(ParameterConstants.NERVOUS_SYSTEM_DOCUMENT, “VALID_DOCUMENT_TYPES”, document.getDocumentType()).evaluateAndAddError(document.getClass(), “errorPropertyName”, “error.invalid.document.type.message”);
In this example, if the value of document.getDocumentType() does not match the values in the parameter, an error will automatically be added to errorPropertyName on the document, and the user message with the key of error.invalid.document.type.message will be shown. Once against, the system parameter’s constraint code is used to determine if the value succeeds or not.
The parameter evaluator can handle more complex situations as well. Take an example where a validation needs to check that, if a business object has a certain “dispersementCode”, then a child business object has a specific “reimbursementCode”. In this case, the system parameter’s value might look like this: A=Z
This means that if the dispursementCode of the parent is A, then the reimbursementCode of the parent must be Z. This parameter can be used with the semi-colon to form a list: A=Z;B=X;C=K
The ParameterEvaluator call is again straightforward:
if (KNSServiceLocator.getParameterService().getParameterEvaluator(this.getClass(), "PARENT_CHILD_MATCH", parent.getDisbursementCode(), child.getReimbursementCode().evaluationSuceeds()) { // do something... }
Here, getParameterEvalutor is given the parameter class, the name of the parameter, the code of the parent and then the code of the child, but works as ParameterEvaluator worked before.
What if the parent’s disbursementCode allowed two different reimbursementCode’s? Then the parameter’s text would look like this: A=Z,Y;B=X,Y;C=K,J,L
Commas separate the child’s distinct values. The invocation of the parameter evaluator is precisely the same as the call above:
ParameterService#getParameterValues() can return a parsed version of a multiple value parameter, and there is a version of ParameterService#getParameterValue() which takes in a constrained value for parameters in the form of “A=B”; if given the value “A”, it will simply return “B”.
All of the methods which use a parameter’s value – ParameterService#getParameterValue, ParameterService#getIndicatorParameter, and ParameterService#getParameterEvaluator – will throw an exception if the system parameter with the specified name cannot be found. If there is an expectation in the code that a parameter may not be found in the database, then it is advisable to call ParameterService#parameterExists method first. If the method returns true, then it is safe to use any of the methods above to utilize the parameter’s value.
This is often useful in cases where there is a parameter that is different from document to document, but for which there exists a default fallback case. It would work like this:
final ParameterService parameterService = KNSLocator.getParameterService(); if (parameterService.parameterExists(document.getClass(), "EXAMPLE_VALUE")) { return parameterService.getParameterValue(document.getClass(), "EXAMPLE_VALUE"); } else { return parameterService.getParameterValue(ParameterConstants.NERVOUS_SYSTEM_DOCUMENT, "EXAMPLE_VALUE"); }
In this example, ParameterService#parameterExists is called to see if there’s a parameter named “EXAMPLE_VALUE” with the namespace and parameter detail code of “document”. If that exists, then it returns the value of that parameter. If it does not exist, it uses the more general KR-NS / Document / EXAMPLE_VALUE parameter.
Rice comes with a number of system parameters which affect KIM, the KNS, and KEW. They have namespace codes “KR-IDM”, “KR-NS”, and “KR-WKFLW” respectively. These provide defaults for Rice behavior which occurs in sample applications.
This poses a problem. If a client application is built to be used with a standalone Rice server, then each client application would have to share the defaults set in these system parameters. To allow client applications to have the ability to set these Rice system parameters separately from other client applications in a shared Rice server, the application namespace code field was added.
For instance, Rice applications come with a system parameter KR-NS / All / DEFAULT_COUNTRY which lists the default country code used in the application. If, for some reason, a client application needed a separate DEFAULT_COUNTRY, a new system parameter would need to be created through the maintenance document. The existing system parameter and the new system parameter would differ only in their values and in their application namespace codes.
All Rice system parameters come with the default Rice application namespace code of “KUALI”. If the client application’s version of the KR-NS / All / DEFAULT_COUNTRY had an application namespace code matching that of the app.namespace configuration property of the client application, then that would be used before the KR-NS / All / DEFAULT_COUNTRY parameter with the “KUALI” application namespace.