Business Objects

 

It may be useful to start with the database table information pertaining to business objects before diving into the Business Object documentation below.

Defining a Business Object implementation

Business objects are java classes that implement the org.kuali.kfs.kns.bo.BusinessObject interface. However, a majority of business objects extend org.kuali.kfs.kns.bo.PersistableBusinessObjectBase, which implements org.kuali.kfs.kns.bo.PersistableBusinessObject and org.kuali.kfs.kns.bo.BusinessObject.

BOs extending org.kuali.kfs.kns.bo.PersistableBusinessObjectBase inherit getters and setters for the object ID  and version number columns, so there is no need to implement them in subclasses.

In each application, all simple class names (i.e. ignoring the package) should be unique. If multiple packages contain the same class name, the data dictionary may not load the duplicated classes properly.

Business objects need to implement getter and setters for each field that is mapped between java business objects and the database table (the mapping is described here). Therefore, if, in java, the ACCOUNT_NM DB column is named "accountName", then the getter method should be called getAccountName and the setter should be setAccountName (i.e. like POJO getters and setters). Application uses reflection API and assume that business object fields follows this naming convention so if methods are named different values will not be read or set correctly.

Objects that extend org.kuali.kfs.kns.bo.BusinessObjectBase must also implement the toStringMapper method, which returns a map of the BO's fields to be used in toString.

The reader should consult the org.kuali.kfs.kns.bo.PersistableBusinessObjectinterface to determine whether there are additional methods that should be implemented/overridden. The interface defines numerous methods that customize the behavior of the business object upon persistence and retrieval of the business object, and how reference objects of the business object are refreshed, as well as other methods.

Reference objects

A reference object is a member variable of a business object that also implements BusinessObject. It refers to the database row referenced by the values in a foreign key relationship. For example, the account BO/table has a column for a chart of accounts ("FIN_COA_CD") code. Therefore, the account BO may have a referenced Chart object, which represents the chart row referred to by the account's chart of accounts code.

A collection reference is a member variable of a business object that implements java.util.Collection, with each element in the collection being a BusinessObject. A collection reference would be appropriate to model the sub accounts of the account business object.

A reference object or collection is defined in two steps:

  1. A field in a business object is created for either the reference object or collection reference
  2. A relationship is mapped within either OJB or the data dictionary. See below for more details.

To refresh (or retrieve) a reference object is to reload the referenced row from the DB, in case the foreign key field values or referenced data have changed.

For references mapped within the data dictionary, the framework does not have the logic to enable refreshing of a reference. The code must both implement the logic to refresh a data dictionary defined reference and the logic to invoke refreshing. This will be explained in the next session.

Refreshing reference objects mapped in OJB

For references mapped within OJB, the framework automatically takes care of the logic to enable refreshing of a reference. Under certain circumstances, it's able to automatically refresh references upon retrieval of the main BO from the database, and refreshing can also be invoked manually.

Note that this means that if the value of a foreign key field is changed, the corresponding reference object is not refreshed automatically. In the account BO implementation (see below), there's both a String for the chart of accounts code, as well as a Chart (i.e. the BO) reference object with the same chart of accounts code. If the code alters the account's chart code, the framework will not automatically retrieve the Chart reference object corresponding to the account's newly set chart code. To refresh the account's chart reference object with the new chart code, refresh/retrieve must be manually called (see below).

Refreshing reference objects not mapped in OJB

For references with relationships that are not mapped in OJB, code will need to be written to accommodate refreshing. A common example of this is Person object references, because as noted here, institutions may decide to use another datasource for Identity Management (e.g. LDAP).

Although there are alternative strategies for accommodating refreshing, getter methods of these non-OJB mapped reference objects include the code that retrieves the reference object from the underlying datasource.

In contrast to OJB-mapped references, note that this strategy allows for the automatic refreshing of reference objects when a foreign key field value has been changed. In the account BO implementation (see below), after the accountFiscalOfficerSystemIdentifier value (a String) is changed, calling the getAccountFiscalOfficerUser() to retrieve the Person reference object corresponding to the newly set identifier value will automatically retrieve the Person object corresponding to the new user ID.

Initializing collection references

Business objects fall into two broad (mostly mutually exclusive) categories: those that are edited by maintenance documents and those that are not. This section refers to business objects that are edited by maintenance documents that have updatable collections.

When constructing this type of BusinessObjects, initialize each of the updatable collection references to an instance of TypedArrayListTypedArrayList is a subclass of ArrayList that takes in a java.lang.Class object in its constructor. All elements of this list must be of that type, and when the get(int) method is called, if necessary, this list will automatically construct items of the type to avoid an IndexOutOfBoundsException. This class allows us to not worry about checking array bounds and constructing items, which allows us to simplify framework code. For example, the SummaryAccount BO contains a updatable reference to a list of PurApSummaryItem objects.

public class SummaryAccount {
    private List<PurApSummaryItem> items;

    public SummaryAccount() {
        super();
        items = new TypedArrayList(PurApSummaryItem.class);
    }
}

When a collection is non-updatable (i.e. read only from the database), it is not necessary to initialize the collection. OJB will take care of list construction and population.

Example Account BO Implementation

Simplified Account Implementation
package org.kuali.kfs.coa.businessobject;

//imports here

public class Account extends PersistableBusinessObjectBase implements AccountIntf, Inactivateable {
    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(Account.class);

    private String chartOfAccountsCode;
    private String accountNumber;
    private String accountName;
    private String accountFiscalOfficerSystemIdentifier;
    private String accountsSupervisorySystemsIdentifier;
    private String accountManagerSystemIdentifier;
    private Chart chartOfAccounts;
    private Person accountFiscalOfficerUser;
    private Person accountSupervisoryUser;
    private Person accountManagerUser;
    private List<ContractsAndGrantsAccountAwardInformation> awards;

    public Account() {
        active = true; // assume active is true until set otherwise
    }
    public String getAccountNumber() {return accountNumber;}
    public void setAccountNumber(String accountNumber) {this.accountNumber = accountNumber;}
    public String getAccountName() {return accountName;}
    public void setAccountName(String accountName) {this.accountName = accountName;}
    public Chart getChartOfAccounts() {return chartOfAccounts;}
    public void setChartOfAccounts(Chart chartOfAccounts) {this.chartOfAccounts = chartOfAccounts;}
    public String getAccountFiscalOfficerSystemIdentifier() {return accountFiscalOfficerSystemIdentifier;}
    public void setAccountFiscalOfficerSystemIdentifier(String accountFiscalOfficerSystemIdentifier) {this.accountFiscalOfficerSystemIdentifier = accountFiscalOfficerSystemIdentifier;}
    public String getAccountManagerSystemIdentifier() {return accountManagerSystemIdentifier;}
    public Person getAccountFiscalOfficerUser() {
        accountFiscalOfficerUser = SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).updatePersonIfNecessary(accountFiscalOfficerSystemIdentifier, accountFiscalOfficerUser);
        return accountFiscalOfficerUser;
    }
    public void setAccountFiscalOfficerUser(Person accountFiscalOfficerUser) {
        this.accountFiscalOfficerUser = accountFiscalOfficerUser;
    }
    public Person getAccountManagerUser() {
        accountManagerUser = SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).updatePersonIfNecessary(accountManagerSystemIdentifier, accountManagerUser);
        return accountManagerUser;
    }
    protected LinkedHashMap toStringMapper() {
        LinkedHashMap m = new LinkedHashMap();

        m.put("chartCode", this.chartOfAccountsCode);
        m.put("accountNumber", this.accountNumber);

        return m;
    }
}

Note that calling setChartOfAccountsCode on an account object will not affect the chartOfAccounts reference object (OJB mapped). Therefore, without refreshing, calling account.getChartOfAccounts().getChartOfAccountsCode() may not equal account.getChartOfAccountsCode().

However, note that calling setAccountFiscalOfficerSystemIdentifier on an account object may affect the accountFiscalOfficerUser reference object (data dictionary mapped) when its getter method is called. This is equivalent to auto-refresh upon changing of the foreign key field.

Creating a data dictionary entry 

Consult this page for more information about how to create a data dictionary file for a business object.

Creating BO-related services

Services such as the BusinessObjectService and PersistenceService are able to retrieve/persist BOs from a database. However, their functionality is limited because they are primarily able only to retrieve BOs based only on "equals" criteria.

To work around the limitation, Services and Data Access Objects (DAOs) should be created. For this section, a simple service will be created that updates all accounts in the system with a given account fiscal officer ID.

About Services and Data Access Objects

A Data Access Object serves as an abstraction layer between the persistence system (e.g. the database) and the business logic layer, which is implemented by services. See the KFS architecture page.

All code specific to the underlying persistence layer (e.g. OJB, JDBC/direct SQL, web service calls, etc.) should be placed in the DAO. The Service layer should contain all of the business logic.

Implementing a Data Access Object

Step 1: Defining a DAO interface

The first step to creating a DAO is to define its interface. Client code will call methods using the interface type, so all methods callable by the other code must be specified in the interface.

package org.kuali.kfs.coa.dataaccess;

public interface AccountDao {
    public void updateAllAccountFiscalOfficers(String userId};
}

Step 2: Implementing the DAO

Currently, there are two primary database access mechanisms for implementing DAOs: JDBC and OJB. This section will provide implementations for the DAO using both mechanisms. Unless performance is a concern, OJB should be used.

JDBC implementation
package org.kuali.kfs.coa.dataaccess.jdbc;

import org.kuali.kfs.coa.dataaccess.AccountDao;
import org.kuali.kfs.kns.dao.jdbc.PlatformAwareDaoBaseJdbc;

public class AccountDaoJdbc extends PlatformAwareDaoBaseJdbc implements AccountDao{

    public void updateAllAccountFiscalOfficers(String userId} {
        getSimpleJdbcTemplate().update("update CA_ACCOUNT_T set ACCT_FSC_OFC_UID = ?", userId);
    }
}

Note the following items about the above code:

  • The class is located in the "jdbc" sub-package of the interface because it's an JDBC based implementation of the DAO.
  • The class extends PlatformAwareDaoBaseJdbc, which extends org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport, which allows calling SQL statements using the getSimpleJdbcTemplate() method. The "platform aware" component of the name refers to the ability to access a dBPlatform property. This property is of type KualiDBPlatform, which provides methods to support platform specific SQL code.

    JDBC DAO's are usually better suited towards operations that involve the manipulation of a large amount of data that can be expressed in SQL. JDBC DAO's are not well suited for returning BO's, because there is a lot of logic involved converting a database row into a BO.

OJB Implementation
package org.kuali.kfs.coa.dataaccess.ojb;
import org.kuali.kfs.coa.dataaccess.AccountDao;
import org.kuali.kfs.coa.businessobject.Account;
import org.apache.ojb.broker.query.Criteria;
import org.apache.ojb.broker.query.QueryByCriteria;

public class AccountDaoOjb extends PlatformAwareDaoBaseOjb implements AccountDao {
    public void updateAllAccountFiscalOfficers(String userId} {
        Criteria criteria = new Criteria();
        Iterator i = getPersistenceBrokerTemplate().getIteratorByQuery(new QueryByCriteria(Account.class, criteria));
        while (i.hasNext()) {
            Account a = (Account) i.next();
            a.setAccountFiscalOfficerSystemIdentifier(userId);
            getPersistenceBrokerTemplate().store(a);
        }
    }
}

Note the following items about the above code:

  • The class is located in the "ojb" sub-package of the interface because it's an OJB based implementation of the DAO.
  • The class extends PlatformAwareDaoBaseOjb, which extends org.springmodules.orm.ojb.support.PersistenceBrokerDaoSupport, which provides a wrapper to OJB's persistence broker by calling getPersistenceBrokerTemplate(). The "platform aware" component of the name refers to the ability to access a dBPlatform property. This property is of type KualiDBPlatform, which provides methods to support platform specific SQL code.
  • Using an Iterator to retrieve results from the database eliminates the need to prefetch all of the accounts in the database into a large list in memory, thus saving time and memory.

Defining DAO Spring beans

Once the implementation class has been defined, a Spring bean needs to be defined for the DAO implementation.

The Spring bean name should be named similarly to the interface and not reflect the underlying mechanism used (i.e. OJB or JDBC).

JDBC DAO Spring Bean definition
<bean id="accountDao" parent="platformAwareDaoJdbc" class="org.kuali.kfs.coa.dataaccess.jdbc.AccountDaoJdbc">
</bean>

Note the following things about the Spring bean definition:

  • It's named "accountDao", which does not imply that JDBC was used to implement the bean.
  • The parent bean is "platformAwareDaoJdbc". All JDBC-based DAOs must use this parent bean.
OJB DAO Spring Bean definition
<bean id="accountDao" parent="platformAwareDao" class="org.kuali.kfs.coa.dataaccess.ojb.AccountDaoOjb">
</bean>

Note the following things about the Spring bean definition:

  • It's named "accountDao", which does not imply that OJB was used to implement the bean.
  • The parent bean is "platformAwareDao". All OJB-based DAOs must use this parent bean.

Implementing a Service

There are several steps to create a service.

Step 1: Defining a service interface

The first step to creating a service is to define its interface.  Client code will call methods using the interface type, so all methods callable by the client must be specified in the interface.

package org.kuali.kfs.coa.service;
import java.util.*;

public interface AccountService {
    public void updateAllAccountFiscalOfficers(String userId};
}

Step 2: Implementing the service

Services are created as Spring beans, so properties should be injected.

package org.kuali.kfs.coa.service.impl;
import java.util.*;
import org.springframework.transaction.annotation.Transactional;
import org.kuali.kfs.coa.dataaccess.AccountDao;
import org.kuali.kfs.coa.service.AccountService;

@Transactional
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;

    // this method is called by Spring during bean factory initialization
    public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }

    public void updateAllAccountFiscalOfficers(String userId} {
        return accountDao.updateAllAccountFiscalOfficers(userId);
    }
}

Note that this service implementation directly calls a DAO method, but more sophisticated logic should be implemented in a service.

Transactional Services

Service implementations that use Iterator and other data structures that are lazily loaded from the underlying data store should be annotated with @Transactional. @Transactional describes transaction attributes on a method or a class.The first method from a class annotated @Transactional in the call stack will start a transaction. When that method terminates (ends, returns, or throws exception), then the transaction will be committed or rolled back. If a transactional method directly or indirectly invokes another transactional method, only one transaction is started. There are ways to start a second transaction, consult the @Transactional javadocs for more details.

Also you may be able to locate a @NonTransactional  which is just a marker, this annotation has no effect in the application at runtime.  It is only used by unit tests which seek to enforce/confirm that the transactional policy is  being applied. Please read javadocs for additional details.

Step: Defining the Spring bean

The following is the Spring bean definition for the AccountService implementation. Note that an AccountDao instance is injected as a property of the service.

<bean name="accountService" class="org.kuali.kfs.coa.service.impl.AccountServiceImpl">
    <property name="accountDao">
        <ref bean="accountDao"/>
    </property>
</bean>

 

 

Kuali documentation is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License. 

Kuali software is licensed for use pursuant to the Affero General Public License, version 3.

 Copyright © 2014 Kuali, Inc. All rights reserved. 

Portions of Kuali are copyrighted by other parties as described in the Acknowledgments screen. 

Kuali ® is a registered trademark of the Trustees of Indiana University.