/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.coa.service.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.Organization;
import org.kuali.kfs.coa.service.ChartService;
import org.kuali.kfs.coa.service.OrganizationService;
import org.kuali.kfs.sys.KFSConstants.ChartApcParms;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.ChartOrgHolderImpl;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.springframework.cache.annotation.Cacheable;
/**
* This class is the service implementation for the Org structure. This is the default implementation, that is delivered with Kuali.
*/
@NonTransactional
public class OrganizationServiceImpl implements OrganizationService {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OrganizationServiceImpl.class);
protected ParameterService parameterService;
protected ChartService chartService;
protected BusinessObjectService boService;
protected Map<ChartOrgHolderImpl,ChartOrgHolderImpl> parentOrgCache = null;
/**
*
* @see org.kuali.kfs.coa.service.OrganizationService#getByPrimaryId(java.lang.String, java.lang.String)
*/
@Override
public Organization getByPrimaryId(String chartOfAccountsCode, String organizationCode) {
Map<String, Object> keys = new HashMap<String, Object>();
keys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
keys.put(KFSPropertyConstants.ORGANIZATION_CODE, organizationCode);
return boService.findByPrimaryKey(Organization.class, keys);
}
/**
* Implements the getByPrimaryId method defined by OrganizationService. Method is used by KualiOrgReviewAttribute to enable
* caching of orgs for routing.
*
* @see org.kuali.kfs.coa.service.impl.OrganizationServiceImpl#getByPrimaryId(java.lang.String, java.lang.String)
*/
@Override
@Cacheable(value=Organization.CACHE_NAME, key="#p0+'-'+#p1")
public Organization getByPrimaryIdWithCaching(String chartOfAccountsCode, String organizationCode) {
return getByPrimaryId(chartOfAccountsCode, organizationCode);
}
/**
* @see org.kuali.kfs.coa.service.OrganizationService#getActiveAccountsByOrg(java.lang.String, java.lang.String)
*/
@Override
public List<Account> getActiveAccountsByOrg(String chartOfAccountsCode, String organizationCode) {
if (StringUtils.isBlank(chartOfAccountsCode)) {
throw new IllegalArgumentException("String parameter chartOfAccountsCode was null or blank.");
}
if (StringUtils.isBlank(organizationCode)) {
throw new IllegalArgumentException("String parameter organizationCode was null or blank.");
}
Map<String, Object> criteria = new HashMap<String, Object>();
criteria.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
criteria.put(KFSPropertyConstants.ORGANIZATION_CODE, organizationCode);
criteria.put(KFSPropertyConstants.ACTIVE, Boolean.TRUE);
return new ArrayList<Account>( boService.findMatching(Account.class, criteria) );
}
/**
* @see org.kuali.kfs.coa.service.OrganizationService#getActiveChildOrgs(java.lang.String, java.lang.String)
*/
@Override
public List<Organization> getActiveChildOrgs(String chartOfAccountsCode, String organizationCode) {
if (StringUtils.isBlank(chartOfAccountsCode)) {
throw new IllegalArgumentException("String parameter chartOfAccountsCode was null or blank.");
}
if (StringUtils.isBlank(organizationCode)) {
throw new IllegalArgumentException("String parameter organizationCode was null or blank.");
}
Map<String, Object> criteria = new HashMap<String, Object>();
criteria.put(KFSPropertyConstants.REPORTS_TO_CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
criteria.put(KFSPropertyConstants.REPORTS_TO_ORGANIZATION_CODE, organizationCode);
criteria.put(KFSPropertyConstants.ACTIVE, Boolean.TRUE);
return new ArrayList<Organization>( boService.findMatching(Organization.class, criteria) );
}
protected void loadParentOrgCache() {
LOG.debug( "START - Initializing parent organization cache" );
Map<ChartOrgHolderImpl,ChartOrgHolderImpl> temp = new HashMap<ChartOrgHolderImpl, ChartOrgHolderImpl>();
Collection<Organization> orgs = boService.findMatching(Organization.class, Collections.singletonMap(KFSPropertyConstants.ACTIVE, true));
for ( Organization org : orgs ) {
ChartOrgHolderImpl keyOrg = new ChartOrgHolderImpl(org);
if ( StringUtils.isNotBlank( org.getReportsToChartOfAccountsCode() )
&& StringUtils.isNotBlank( org.getReportsToOrganizationCode() ) ) {
ChartOrgHolderImpl parentorg = new ChartOrgHolderImpl( org.getReportsToChartOfAccountsCode(), org.getReportsToOrganizationCode());
temp.put(keyOrg, parentorg);
}
}
parentOrgCache = temp;
if ( LOG.isDebugEnabled() ) {
LOG.debug( "COMPLETE - Initializing parent organization cache - " + temp.size() + " organizations loaded" );
}
}
@Override
public void flushParentOrgCache() {
LOG.debug( "Flushing parent organization cache" );
parentOrgCache = null;
}
@Override
public boolean isParentOrganization( String childChartOfAccountsCode, String childOrganizationCode, String parentChartOfAccountsCode, String parentOrganizationCode ) {
if (StringUtils.isBlank(childChartOfAccountsCode)
|| StringUtils.isBlank(childOrganizationCode)
|| StringUtils.isBlank(parentChartOfAccountsCode)
|| StringUtils.isBlank(parentOrganizationCode) ) {
return false;
}
if ( parentOrgCache == null ) {
loadParentOrgCache();
}
ChartOrgHolderImpl currOrg = new ChartOrgHolderImpl( childChartOfAccountsCode, childOrganizationCode );
ChartOrgHolderImpl desiredParentOrg = new ChartOrgHolderImpl( parentChartOfAccountsCode, parentOrganizationCode );
// the the orgs are the same, we can short circuit the search right now
if ( currOrg.equals( desiredParentOrg ) ) {
return true;
}
return isParentOrganization_Internal(currOrg, desiredParentOrg, new ArrayList<ChartOrgHolderImpl>() );
}
/**
* This helper method handles the case where there might be cycles in the data.
*
*/
protected boolean isParentOrganization_Internal( ChartOrgHolderImpl currOrg, ChartOrgHolderImpl desiredParentOrg, List<ChartOrgHolderImpl> traversedOrgs ) {
if ( traversedOrgs.contains(currOrg) ) {
LOG.error( "THERE IS A LOOP IN THE ORG DATA: " + currOrg + " found a second time after traversing the following orgs: " + traversedOrgs );
return false;
}
ChartOrgHolderImpl parentOrg = parentOrgCache.get(currOrg);
// we could not find it in the table, return false
if ( parentOrg == null ) {
return false;
}
// it is its own parent, then false (we reached the top and did not find a match)
if ( parentOrg.equals(currOrg) ) {
return false;
}
// check parent org against desired parent organization
if ( parentOrg.equals( desiredParentOrg ) ) {
return true;
}
// otherwise, we don't know yet - so re-call this method moving up to the next parent org
traversedOrgs.add( currOrg );
return isParentOrganization_Internal(parentOrg, desiredParentOrg, traversedOrgs);
}
/**
*
* @see org.kuali.kfs.coa.service.OrganizationService#getActiveOrgsByType(java.lang.String)
*/
@Override
public List<Organization> getActiveOrgsByType(String organizationTypeCode) {
if (StringUtils.isBlank(organizationTypeCode)) {
throw new IllegalArgumentException("String parameter organizationTypeCode was null or blank.");
}
Map<String, Object> criteria = new HashMap<String, Object>();
criteria.put(KFSPropertyConstants.ORGANIZATION_TYPE_CODE, organizationTypeCode);
criteria.put(KFSPropertyConstants.ACTIVE, Boolean.TRUE);
return new ArrayList<Organization>( boService.findMatching(Organization.class, criteria) );
}
/**
*
* @see org.kuali.kfs.coa.service.OrganizationService#getActiveFinancialOrgs()
*/
@Override
public List<Organization> getActiveFinancialOrgs() {
Map<String, Object> criteria = new HashMap<String, Object>();
criteria.put(KFSPropertyConstants.ORGANIZATION_IN_FINANCIAL_PROCESSING_INDICATOR, Boolean.TRUE);
criteria.put(KFSPropertyConstants.ACTIVE, Boolean.TRUE);
return new ArrayList<Organization>( boService.findMatching(Organization.class, criteria) );
}
/**
*
* TODO: refactor me to a ChartOrgHolder
*
* @see org.kuali.kfs.coa.service.OrganizationService#getRootOrganizationCode()
*/
@Override
public String[] getRootOrganizationCode() {
String rootChart = chartService.getUniversityChart().getChartOfAccountsCode();
String selfReportsOrgType = parameterService.getParameterValueAsString(Organization.class, ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES);
String[] returnValues = { null, null };
Map<String, Object> criteria = new HashMap<String, Object>();
criteria.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, rootChart);
criteria.put(KFSPropertyConstants.ORGANIZATION_TYPE_CODE, selfReportsOrgType);
criteria.put(KFSPropertyConstants.ACTIVE, Boolean.TRUE);
Collection<Organization> results = boService.findMatching(Organization.class, criteria);
if (results != null && !results.isEmpty()) {
Organization org = results.iterator().next();
returnValues[0] = org.getChartOfAccountsCode();
returnValues[1] = org.getOrganizationCode();
}
return returnValues;
}
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
public void setBusinessObjectService(BusinessObjectService boService) {
this.boService = boService;
}
public void setChartService(ChartService chartService) {
this.chartService = chartService;
}
}