The Principles of Object-oriented Design

Object-oriented design forces software developers to think in terms of objects, rather than procedures. It is based on the following major techniques: inheritance, polymorphism, encapsulation, and modularity. A set of principles are derived from those techniques and can help us have better understanding about object-oriented design.

Single Responsibility Principle 

A class has a single responsibility: it does it all, does it well, and does it only.

Following this principle usually leads to simpler code which is in turn easier to understand, design, and maintain. This principle can also be applied to a method or even a member variable.  If a class or method looks too big, it likely has too many responsibilities and probably ought to be refactored into additional methods or even additional classes.

Suggestions:

Open Close Principle

Software entities (classes, modules, functions, and so forth) should be open for extension, but closed for modification.

This principle's guidelines are twofold:

Our take on this will be slightly different, as we are writing an application which we know that the implementors will want to customize. As such, we need to be careful not to lock them out of customizing pieces of the application. Where we can anticipate customization, we should add hook points to allow for easy overrides. But, private variables and methods interfere with their ability to override something which we did not anticipate.

Program to an Interface

Program to an interface, not an implementation

An interface specifies the behaviors of an object without defining its implementation. The interface can be implemented by one or more classes with different flavors of logics.  In order to increase the reusability of our classes, we want to limit their dependencies on other classes as much as possible. “Program to an interface, not an implementation” is just a powerful and simple way to reduce the dependencies among classes.

The two methods in the following code actually perform the same logic: to get the values from the given key-value pairs. The first method uses the argument of type Map and is able to accept any object of a class implementing the Map interface, for example, EnumMap and TreeMap.  The second method, on the other hand, can only accept an object of HashMap or its subclasses. Obviously, the second method is greatly dependent on HashMap, which is not desired.

 
void retrieveValuesFromMap(Map<String, String> fieldValues); 

void retrievevaluesFromHashMap(HashMap<String, String> fieldValues); 

Suggestions:

This principle applies mainly to services. Our business objects and documents do not follow this principal and the core system assumes concrete classes in a nuber of places.

Java Programming Paradigms

Enumerated Type

An enumeration is new to Java 5. It is an object type with a finite set of possible values, which is called enum values or enum constants.

Prior to Java 5, public static final int declarations were used as enum values. Here is an example:

 
public static final int SPRING = 0; 
public static final int SUMMER = 1; 
public static final int AUTUMN = 2; 
public static final int WINTER = 3; 

double getAverageTemperatureOfSeason(int season); 

The declarations are still open to another integer values, and even invalid values. For example, “5’ can be passed to the method getAverageTemperatureOfSeason(int). Compared to the public static final int declarations, enumerations provide type safety.  In the code below, the four enum values are the only legal parameters for the method getAverageTemperatureOfSeason(Season). If an object of another type is passed in, Java compiler will complain about it.

 
public enum Season { SPRING, SUMMER, AUTUMN, WINTER }; 
  
double getAverageTemperatureOfSeason(Season season);  

Suggestions:

Annotations

Java annotations allow developers to add metadata to source code. The metadata can be utilized by tools at compile time and retained in the compiled Java classes for use at runtime. Annotations should never affect the way a Java program runs, but they may affect the behaviors of a compiler or auxiliary tools. For example, the annotation @SuppressWarnings instructs Java compiler to suppress the specified warning types while the annotation @Transactional in Spring is used to let the application put the calling class or method in a transactional context.

Currently, there are a few annotations that are available for Kuali Projects:

Java Language Specification requires annotations are not supposed to “directly affect the semantics of a program”. However, there are few standards that dictate what metadata should be used and carried by annotations, so developers are suggested to use annotations with restraint.

Annotation Examples

Java Generics

Generics were implemented by Java 5 or later. Generics make Java code easier to write and read through making the type of the objects an explicit parameter of the generic code.  Using Generics gives us:

Here, we would like to promote the usage of Java generics :

Method-Chaining

A few of programming languages and Java applications are promoting the use of method chaining since it is short and simple. The following code snippet is an example of Java method chaining:

 
getAccount().getChartOfAccounts().getFinancialObject().getName(); 

This is very convenient, but comes at a cost. When using this syntax, you have no protection from NullPointerException}}s if a link in the chain does not exist. As such, even though it is a pain, you must check the pieces of the chain in sequence. It is not acceptable to catch the {{NullPointerException, as exceptions should only be used for unanticipated errors, not ones which you could reasonably expect. So the above line would probably have to look something like this:

 
String name = "Unknown Object Code"; 
if ( getAccount() != null 
&& getAccount().getChartOfAccounts() != null 
&& getAccount().getChartOfAccounts().getFinancialObject() != null ) { 
name = getAccount().getChartOfAccounts().getFinancialObject().getName(); 
} 

This works because the Java && operator "short-circuits" the expression if the result is known after a certain point. Java evaluates expressions left-to-right, and knows to terminate as soon as it sees a "false" result.

The code above assumes normal Java objects. Care must be taken when working with objects retrieved from the database via OJB. If an object is automatically retrieved, as the Chart object from the getAccount().getChartOfAccounts() call will be, it will not be null. However, calling getAccount().getChartOfAccounts().getFinancialObject() will throw an error if the chart does not exist. The problem is that the system will return a proxy object created by OJB, which is not null, but also can not be used. OJB will attempt to load the object from the database upon use of the object. In any case where the object was retrieved from the database automatically, you must use a call like:

org.kuali.rice.kns.util.ObjectUtils.isNotNull( getAccount().getChartOfAccounts() )

which performs the proper checks knowing how to resolve the OJB proxy class.

 Scopes of Variables

Java has three kinds of variables:

Suggestions:

Configurability

Levy's Eighth Law: No amount of genius can overcome a preoccupation with detail.

The Law above shows that details are difficult to deal with. For a business-oriented application, details can be everything that needs to be changed with time, such as business rules, constants, and prompt text. If the details are not appropriately manipulated, they can be duplicated and mingled with business logics, which makes the application code hard to learn and modify. Whenever changing the code to accommodate new requirements, we run the high risk of breaking it because failing to uncover all details related to the change.

Ideally, an application should be highly configurable -- all details should be declared out of application code, a set of metadata are defined to represent the details, and the code only interacts with the metadata, instead of the details themselves.

Suggestions:

Services

Application Architecture and Design Patterns

Component-based multi-tier application architectures have been invented and applied for a long time. Those architectures can greatly help us organizing the application components and promoting their reusability by defining layers of responsibilities.

An application typically has capabilities to access data sources, apply business logic and generate system views for users. Those capabilities can be provided by three components in three layers:

The architecture logically divides the entire application functionalities into three components and places them in separate logical layers. In the implementation of an application, an object called business object can be used to glue the three layers together by facilitating uniformed data representation.

Within each layer, there might be one or more design patterns applied to implement corresponding component. Four design patterns will be introduced here.

Business Object/Transfer Object

(Avoid complex business logics, rather than put them into business service)

A business process manages business data and business logic/rules. Business data usually Business Objects is used to separate business data and logic.

Application Service

(Avoid direct external resource access)

Data Access Object

(Database, file system and other external data sources)

Model-View-Controller

Suggestions:

Thread Safety

All web applications are multi-threaded applications. This means that a particular section of code may be accessed at the exact same time by multiple users. As such, you must never store an operation's state in a variable which could be accessed from multiple threads. In the best case, an error will occur when the user's sessions collide. In the worst case, there would be silent data corruption as one user's data is saved over the other's.

This is why all services in the application must be stateless, depending only on their passed parameters.

Getters and Setters

In general, any method in the form of "<some type> get{PropertyName}()" should be a real getter: it should not take any function parameters and it should return the value of a property.  The same with setters - they should return void, take in one parameter, and set the value of the property to that parameter.  If a getter is returning a primitive boolean, then it should be an "is-er" - ie, it should be in the form of "boolean is{PropertyName}()".  These conventions, part of the original JavaBean spec, are well established.  This implies that, for instance, setting the value a member variable in a getter or that a setter which reads from a database are probably preforming behavior which other developers would not expect.  We'd like to try to avoid those kinds of situations.

There are, of course exceptions.  For instance, KFS has a pattern of using a getter to return services in contexts where Spring cannot inject services - ie, where the service needs to be retrieved from SpringContext directly as there is no other choice about the service's retrieval.  That pattern looks commonly like this:

class ExampleDocument {
    private static volatile BusinessObjectService businessObjectService;

    public BusinessObjectService getBusinessObjectService() {
        if (this.businessObjectService == null) {
            businessObjectService = SpringContext.getBean(BusinessObjectService.class);
        }
        return businessObjectService;
    }
}

Here, we've got a document which wants to use a BusinessObjectService.  Ideally, we'd like to cache that BusinessObjectService variable in such a way that all instances of the document live in memory can share it - hence we declare the property to be static.  (We also want calls to getBusinessObjectService() running concurrently to not write over the businessObjectService property multiple times - that's why we declare the property to be volatile).  If the businessObjectService property is null, we go ahead and set it from SpringContext, and then we return that property.  This means we're possibly setting the value in the getter.  In this case, though, we find the setting excusable since: a) we know that no other setting of businessObjectService will ever occur; b) we always use getBusinessObjectService(), never the property businessObjectService directly; and c) the setting will only ever occur once.  These constraints make this an unusual case.  In the vast majority of cases, though, getters and setters should truly be getters and setters.

Concrete methods on Interfaces

Java 8 comes with a lot of revolutionary language features.  However, to enable those revolutionary language features, the Java team introduced one ugly feature: the ability to write concrete methods on Interfaces.  The rationale behind this is that in some special cases, a method has a single, standard implementation which should always be used.

Hogwash!  Malarkey!  The real reason this feature was introduced was to make it easy to access Streams from Collections in a way that would retain Java's backwards compatibility, because if backwards compatibility wasn't there, Java programmers would grouse and pout.  The Java designers did give us a fair number of revolutionary features, though, and therefore, we forgive them.

However, just because the Java language team used this doesn't mean any of the rest of us should.  It's a really strange feature and it should be avoided if at all possible.

Java 8 Streams

Java 8 introduced the concept of a stream that can be filtered, manipulated and collected into a new data type.  These features make code shorter and more readable.  When working with collections, streams should be used when possible in place of code that loops through each element.  Information on Java 8 Streams can be found here: Processing Data with Java SE 8 Streams.

Javadoc/Comment Standards

Taken from Coding Standards

Javadoc Standards 

The Javadoc standard is a way of writing comments in code that can be used when generating documentation web pages.  These forms of comments should be used for classes and methods when the code warrants a comment.  Comments should be provided to explain why a class exists or why a method is needed.  It should also explain something that is non-obvious in the code.  A comment should not state the obvious (examples "This is the default constructor" or "getter for accountName").

Class Javadocs

Method Javadocs

Taken from KC Coding Standards - Post Release 1.0

General Comments about Comments

Taken from Javadoc Standards

Examples

Class Headers

 
/** 
 * This is a description of what this class does... 
 */ 
public class ClassName { ... 

Javadocs for Methods

 
/** 
 * A useful description of what this method does... 
 *
 * @param paramOne This param... 
 * @param paramTwo This param... 
 * @return Returns the... 
 * @throws ExceptionClassName This is thrown when... 
 */ 
public ClassName doMethod(ClassNameOne paramOne, ClassNameTwo paramTwo) throws ExceptionClassName { ... 

If your method is in an implementation class, your Javadocs should follow this format:

 
/** 
 * A useful description of what this method does... how does this implementation class 
 * implement the interface. What technology does it use? Is this the default impl? What's 
 * unique about this implementation of the method? 
 *
 * @see org.kuali.service.SomeInterfaceClass#doMethod(ClassNameOne classNameOne, ClassNameTwo classNameTwo) 
 */ 
public ClassName doMethod(ClassNameOne classNameOne, ClassNameTwo classNameTwo) throws ClassName { ... 

If your method is in an interface, you Javadocs should follow the standard method format, describing what the method does without describing any implementation details.

Constructor Javadocs

 
/** 
 * This constructor sets up empty instances for the dependent objects... 
 */ 
public ClassName() { ... 

Java Style Guidelines

In general, we are following the recommendations in the book: The Elements of Java Style. Please be familiar with the conventions in that book, as it will result in less reformatting of code later. (Such reformatting makes it harder to detect actual coding changes.)

Use of Braces

In almost all cases, braces should be used when creating blocks. It is too easy to introduce bugs into the system by omitting braces for single-line clauses, especially if-then-else setups. (This is one of those cases where indentation comes into play, if lines after the first are also indented, a developer reading the code might assume that those lines are part of the else clause.

The only exception would be simple one-line statements which are on the same line as the if statement. The statement and if cause must be simple so the line is not too long to easily read. E.g,:

 
// return false if the passed in argument does not have a value 
if ( StringUtils.isBlank( chartOfAccountsCode ) ) return false; 

This helps save space and is still easy to read. Be sure to have a blank line after a statement like this so it is evident that there are no other statements within the if block.

Naming Conventions

General Java Naming Conventions

In the following table, the naming conventions for Java language entries are listed as well as corresponding examples:

Entity

Naming Conventions

Good Examples

Incorrect Examples

package

  • reverse name of internet domain and suffix the package name
  • use a single, lowercase word as each package name
  • meaningful abbreviations can be used

org.apache.log4j.logger
org.kuali.ole.module.ld
org.kuali.ole

org.kuali.YearEnd

class

  • use nouns when naming classes
  • use full description with the first letters of all words capitalized
  • pluralize the names of classes that group related attributes, static services and constants

LedgerPendingEntry
BalanceType
PendingEntryGenerator
LaborConstants

BalanceTyp

interface

  • use nouns or adjectives when naming interfaces
  • use full description with the first letters of all words capitalized
  • avoid using suffixes like "Interface", "Intf", or "I" in the name (preference)

TransactionPoster
LookupResultsSelectable
AccountInterface
LedgerEntryService

PostTransaction
AccountIntf

enum

  • use nouns when naming enumerations

Weekday

 

enum values

  • use full description with uppercase words separated by underscores for enum constants

MONDAY
DIVIDED_BY

 

method

  • use active verbs when naming methods
  • use full description with lowercase of the first word and the first letters of all subsequent words capitalized
  • follow java bean conventions for naming property accessor and mutator methods

save()
updateLedgerBalance()
getLedgerBalance()
setLedgerBalance()
isValidAccount()

 

variable

  • use nouns when naming variables
  • use full description with lowercase of the first word and the first letters of all subsequent words capitalized
  • prefix the variables of type Boolean with “is” , “can” and so on to indicate their types
  • pluralize the names of collection references

documentTypeCode
financialObjectCode
entryIterator
isNewAccount
canOpenNewAccount

findTypeCode
foc
entry_iter
newAccount
openNewAccount
subFundGroupCodes

constant

  • constants should be defined as static final variables
  • use nouns when naming constants
  • use full description with uppercase words separated by underscores
  • if the constants are part of a closed set, consider using an enum

FISCAL_YEAR

FROM
fiscalYear
Fiscal_Year

Suggestions:

This section is the Original Java Naming Conventions text. (the above was copied from the OLE site)

General Naming Convention

Naming convention make programs more understandable by making them easier to read. They can also give information about the function of the identifier-for example, whether it's a constant, package,

or class-which can be helpful in understanding the code

Identifier Type

Rules for Naming

Examples

Packages

The prefix of a unique package name is always written in all-lowercase ASCII letters and should be one of the top-level domain name, org . Subsequent components of the package name vary according to modules, businessObject, document, service, dao etc.

org.kuali.kfs.pdp.businessobject
org.kuali.kfs.fp.document.authorization;


Classes

Class names should be nouns, in mixed case with the first letter of each internal word capitalized. Try to keep your class names simple and descriptive. Use whole words-avoid acronyms and abbreviations (unless the abbreviation is much more widely used than the long form, such as URL or HTML).

AdvanceDepositAccountingLineAuthorizer
ACHBank, PaymentACHAccount

Interfaces

Interface names should be capitalized like class names

interface AccountingLineAuthorizer, FinancialSystemTransactionalDocumentPresentationController

Methods

Methods should be verbs, in mixed case with the first letter lowercase, with the first letter of each internal word capitalized. The method names should be meaningful that succinctly describe the purpose of the method, making the code self-documenting and reducing the need for additional comments.

extractAccruals(),needsExpiredAccountOverride

Variables

Except for variables, all instance, class, and class constants are in mixed case with a lowercase first letter. Internal words start with capital letters. Variable names should not start with underscore _ or dollar sign $ characters, even though both are allowed.Variable names should be short yet meaningful. The choice of a variable name should be mnemonic- that is, designed to indicate to the casual observer the intent of its use. One-character variable names should be avoided except for temporary "throwaway" variables. Common names for temporary variables are i, j, k, m, and n for integers; c, d, and e for characters.

String fullParameter,String boClassName

Constants

The names of variables declared class constants and of ANSI constants should be all uppercase with words separated by underscores ("_"). (ANSI constants should be avoided, for ease of debugging.)

public static final Integer EXPIRED_ACCOUNT , List<String> REFRESH_FIELDS

Logging

Kuali defaults logging to the INFO level. This means that, in production, any messages at INFO level or higher will go into the application logs. Since this is the primary means of monitoring system operation, we need to be careful to not overload these logs with too much detail, as that tends to obscure any problems which may occur. (It may also cause critical troubleshooting information to be rotated out of the logs too soon.)

If it's for development purposes, put any logging at DEBUG or TRACE levels. Logging levels can always be increased as needed, even on production servers. But when logging anything which is not a problem, be sure that the only code which executes when the logging will not be displayed is the check itself. That is, don't assemble the message unless you are going to output it.

Remember that in a call like:

LOG.debug( "Checking contents of array: " + Arrays.asList( someArray ) ); 

That Java has to first run the Arrays.asList() method, convert it to a string, build a StringBuilder, append the strings, and re-convert the result into a String. Then, it can pass it to the LOG.debug() method which decides whether to show the result. Instead, use a form like the below, as it skips the assembly of the message unless it knows it will be displaying it. (Yes, the logging level check is done twice, but that is acceptable since it will only be done twice if DEBUG logging is turned on.

if ( LOG.isDebugEnabled() ) { 
	LOG.debug( "Checking contents of array: " + Arrays.asList( someArray ) ); 
} 

If you are only displaying a static string or a single string variable, no extra check is necessary, as no processing has to be done to pass that into the method.

When declaring a Logger:

For example:

private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BankServiceImpl.class);

Exception Handling

Java defines two kinds of exceptions: checked exceptions and unchecked exceptions.

An exception should be caught and handled explicitly in the following cases:

Multi-catch

Suggestions:

Method Visibility

Unless child classes really should not be modifying a variable, make all member variables and methods of a class protected. Kuali applications are often extended by implementors and private member variables make that difficult.

Inject All Services

All services used by another service (or any class which is instantiated via Spring) should be injected via the Spring XML files. This forms a kind of documentation as to what services are being used by the service. These service references should be stored in instance variables of the class.

Of course, if the class (such as a document) is not generated by Spring, then the services must be obtained via the SpringContext.getBean() method. However, references to these services should be stored in static class variables and retrieved by code via getter methods like the following. This performs the lookup as late as possible and only if needed as well as prevents excessive service lookups.

 
// this variable is private to ensure that subclasses only use the getter 
private static DataDictionaryService dataDictionaryService; 

// This method is protected. Nothing but this class and subclasses should ever obtain the service reference from here. 
protected DataDictionaryService getDataDictionaryService() { 
    if ( dataDictionaryService == null ) { 
        dataDictionaryService = SpringContext.getBean(DataDictionaryService.class); 
    } 
    return dataDictionaryService; 
} 

Service Injection Exceptions

There are a couple exceptions to this rule.

  1. Some services/beans have scope="prototype" in their definition. A new instance of these beans are created every time it is retrieved. These beans should never be cached, as they, in most cases, store session-specific data.
  2. KEW, KSB, and KIM services should be obtained upon need. These services maybe accessed from a remote Rice server at some point, so we should not assume we can hold a static reference to a remote service, since such a reference is bound to a particular physical node.

Do Not Access Services in Static Initializers or Constructors

When classes are instantiated, it can be tempting to initialize service references upon class load or instance creation. Both of these must be avoided. We have no control over when classes are loaded and we do not know when classes may be instantiated. In many cases, these actions take place before the application has completed startup. Accessing the service bus at that time will often result in an error and cause the application startup to fail. (Most business objects and document classes are instantiated during data dictionary validation, so this is a real potential problem. Instead, use the model above to only obtain the service/bean reference upon first demand.

Third-Party Libraries

There are many excellent Java Libraries available which perform operations which are often needed by software applications. Many of the low-level issues we encounter as programmers have already been dealt with, debugged, and used in production applications. We should not re-invent these operations. If you are attempting to perform what seems like a low-level manipulation of data or network operation, please look at the existing libraries in the project, especially the Apache Commons libraries.

Apache Commons libraries

As previously mentioned, the Apache Commons libraries contains classes with a lot of good low-level methods that can be used without developers having to "re-invent the wheel" or use less "safe" alternatives. Here are a couple good examples:

Adding New Libraries

All Java libraries delivered with Kuali Project must conform to Open Source licenses in order to avoid legal issues. Before a new third party library is introduced, its license should be submitted and evaluated by the Project. Here is the basic evaluation procedure:

3rd Party Evaluation Process

Suggestions:

KFS Constant Classes

One of the General Coding Standards for KFS is "Use constants instead of string literals." A developer might ask, "Where is the best place to put my constant?" There are a variety of contants classes in KFS both global to KFS and specific to individual modules. Put your constant in the most fine-grained location possible that is specific to the type of constant and the module that will be using it. For example, an error key constant used by multiple modules would go in the global org.kuali.kfs.sys.KFSKeyConstants class, while an error key constant that was specific to the PurAP module would go in the org.kuali.kfs.module.purap.PurapKeyConstants class.

Examples of Constant Classes:

 

Constant TypeGlobal KFS ClassModule-Specific class exampleDescription/Usage
Constants

org.kuali.kfs.sys.KFSConstants

org.kuali.kfs.module.purap.PurapConstantsUsed to define general constants (don't fit in one of the categories below) for the system or module.

KeyConstants

org.kuali.kfs.sys.KFSKeyConstantsorg.kuali.kfs.module.purap.PurapKeyConstantsHolds error key constants that are global or module-specific.
PropertyConstantsorg.kuali.kfs.sys.KFSPropertyConstantsorg.kuali.kfs.module.purap.PurapPropertyConstantsHolds property name constants that are global or module-specific.

ParameterConstants

org.kuali.kfs.sys.KFSParameterKeyConstantsorg.kuali.kfs.module.purap.PurapParameterConstantsHas names of parameters associated globally or with a module (inner classes can be used to organize those parameters which don’t fit with the big four components of All, Batch, Document, and Lookup).
WorkflowConstants org.kuali.kfs.module.purap.PurapWorkflowConstantsHas node names and application document statuses (inner classes can be used here to differentiate between nodes and statuses used on different documents, like what PurapWorkflowConstants does).
AuthorizationConstantsorg.kuali.kfs.sys.KfsAuthorizationConstantsorg.kuali.kfs.module.purap.PurapAuthorizationConstantsContains names of edit modes (again, inner classes to separate by document type is fine) and special permission names which need to be called...and in very very rare cases, role names.
KimAttributesorg.kuali.kfs.sys.identity.KfsKimAttributesorg.kuali.kfs.module.purap.identity.PurapKimAttributesSpecial case that holdes constants for KIM attributes as well as instance variables, setters and getters for those attributes.

KFS Naming Standards

These are examples of naming convention for KFS documents.

BO or Transactional Document Class

BO DD Entry

Doc DD Entry

Workflow Doc Name

Doc Label / Workflow Doc Label / Portal Link

AssetAcquisitionType.java

AssetAcquisitionType.xml

AssetAcquisitionTypeMaintenanceDocument.xml

AssetAcquisitionTypeMaintenanceDocument

Asset Acquisition Type

AssetDepreciationMethod.java

AssetDepreciationMethod.xml

AssetDepreciationMethodMaintenanceDocument.xml

AssetDepreciationMethodMaintenanceDocument

Asset Depreciation Method

AssetObjectCode.java

AssetObjectCode.xml

AssetObjectCodeMaintenanceDocument.xml

AssetObjectCodeMaintenanceDocument

Asset Object Code

Module Development Standards

These standards are concerned with ensuring the smoothest sailing possible for implementers who need to switch out one of our optional module implementations. All but Chart, Financial Processing, General Ledger, Pre-Disbursement Processing, and Vendor are optional modules.

For those modules that are not part of the core financial system, the following should be developed to account for java dependencies

When one module needs to display information from another module, Externalizable Business Objects should be used. When a module needs to collect additional information for another module, Externalizable Business Objects should be used to determine how to draw the input fields, and the depended on module service should be responsible for the validation (see CapitalAssetInformationValidation). System parameters related to module interfaces should be associated with the depended on module, e.g. CAB parameters related to FP and PURAP collection of CAB data. The data ownership is with CAB, so that is where the parameters belong.

All services / utilities used by more than one optional module will need to reside in org.kuali.kfs.* per the restrictions on cross-optional module code dependencies. When moving these types of things into KFS core, create an RNE jira to document the work as usual.

In rare cases, we will need to use table(s) as an interface between two optional modules. Those use cases and the specific tables involved were documented on the archived Tabular Inter-Module Interfaces page.

OJB Proxying

OJB Proxies and testing for nulls

Static imports / Importing Inner Classes

Static imports (and their cousin - Importing Inner Classes) can be ok in unit test classes, but should be avoided in non-test code. This is to support readability and clarity of code. Consider "CamsConstant.AssetRetirementReasonCode.GIFT" and the following scenarios:

Util classes vs. Services

KFS Currently has several *Util classes typically with static methods. However, Services with public methods on the service interface are preferable to static methods in Util classes, so new code should be placed in public service methods rather than static Util methods.

Using Rice/Client Framework Services

There are a large number of services in the Rice stack. However, not all of them are meant for use by client applications. The vast majority of the services are only used internally by the various modules. The greatest number of services exist in the KEW module. However, only a handful should be used.

When writing Rice-based applications, you will generally only access services from the KIM and KEW modules in normal operation. KNS services all belong to the client and can be accessed freely.

When there is a choice between a service in the org.kuali.rice or org.kuali.kfs package, prefer the org.kuali.kfs package service.

General Rule for KEW Services

I will list some of the services later, but in general, within the KEW module, if the service returns objects whose names end in "Value", then it is an internal service and should not be used.

Public services in KEW all return "DTO" objects which are capable of being sent over the Kuali Service Bus.

Useful KNS Services

Service

Purpose

Sample Methods

Standard KNS Services

 

 

BusinessObjectService

General purpose service to retrieve and persist business objects. This service eliminates the need for custom DAOs and services for basic CRUD operations.

save( businessObject ) 
findByPrimaryKey( class, primaryKeyMap ) 
findBySingleFieldPrimaryKey( class, primaryKeyValue ) 
findMatching( criteriaMap ) 
countMatching( criteriaMap )

LookupService

Service for performing more complex lookups of business objects. Criteria maps passed to this service can contain special characters in the lookup values which can perform and/or/between/greater than/less than operations. This also can be used to reduce the need for OJB-specific query code.

findCollectionBySearch( class, criteriaMap )

MailService

Used to send email messages.

sendMessage( MailMessage )

DateTimeService

Use to obtain the current date/time and parse/format dates between String and Date objects.

getCurrentDate() 
getCurrentSqlDate()

Data Dictionary Services

 

 

DataDictionaryService

General purpose service to obtain information about business objects, documents, and their attributes from the data dictionary. These calls are lower level than either of the services below. Only use these calls if you can't get the information from one of the services below.

 

BusinessObjectDictionaryService

Specialization of the above service to help answer more complex, but common, questions needed about business objects, mainly for implementing the KNS framework.

 

MaintenanceDocumentDictionaryService

Same as the above, but for maintenance documents.

Class getMaintainableClass(String docTypeName) 
String getDocumentTypeName(Class businessObjectClass) 
Class getBusinessObjectClass(String docTypeName)

Workflow Wrapper Services

 

 

DocumentService

Primary KNS service for interacting with documents. This service should be used whenever possible instead of accessing KEW services.

Document getNewDocument(String documentTypeName) 
boolean documentExists(String documentHeaderId) 
Document getByDocumentHeaderId(String documentHeaderId) 
Document saveDocument(Document document) 
routeDocument(...) 
approveDocument(...) 
sendAdHocRequests(...)

Information Services

 

 

ConfigurationService

Used to pull configuration properties (mostly from configuration.properties).

String getPropertyValueString(String key)
boolean getPropertyValueAsBoolean(String key)

ParameterService

Used to pull parameters from the KRNS_PARM_T table on the Rice server.

parameterExists(...) 
getParameterValueAsBoolean(...) 
getParameterValueAsString(...) 
getParameterValuesAsString(...) 

Public KEW Services

In general, the services below should only be used if there is not a KNS "wrapper" service for the same operation. Often, the KNS services perform additional actions on the document before calling into the workflow engine.

Service

Purpose

Sample Methods

WorkflowUtility

main service for obtaining information about a document in routing

  • getRouteHeader(docNum)
  • getDocType(docTypeCode)

WorkflowDocumentActions

allows most actions to be taken on an existing document in routing

  • superUserApprove(String principalId, RouteHeaderDTO routeHeader, String annotation)
  • adHocRouteDocumentToPrincipal(String principalId, RouteHeaderDTO routeHeader, String actionRequested, String nodeName, String annotation, String targetPrincipalId, String responsibilityDesc, boolean forceAction, String requestLabel)

DocumentSearchService

Perform document lookups using the same APIs as the document search screen.

For programmatic searches, DocumentSearchService has a number of problems - it's incredibly slow, and furthermore, it brings back a limited number of results. Because of this, it would be best to consider if you can find all of the information needed to successfully complete the search by doing a BusinessObjectService#findMatching on records of FinancialSystemDocumentHeader.

DocumentSearchResultComponents getList(String principalId, DocSearchCriteriaDTO criteria)

Public KIM Services

Service

Purpose

Sample Methods

IdentityService

Service to access principal and entity information.

getPrincipalByEmployeeId()
getPrincipalByPrincipalName()

RoleService

Information about Roles

getRole()
getRoleByNamespaceCodeAndName()
PermissionServiceService which checks if a given principal has the permission to execute a certain kind of system event.
isAuthorizedByTemplate()
findPermissionsByTemplate()
PersonServiceFull information about entities stored in KIM.  IdentityService's getPrincipal*() methods are much faster and typically require all the information necessary to do, say, a permission check - for this reason, those methods are preferred to using PersonService.
getPersonByPrincipalName()