001/*
002 * Copyright 2005 The Kuali Foundation
003 * 
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 * http://www.opensource.org/licenses/ecl2.php
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.coa.service.impl;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.commons.lang.StringUtils;
026import org.kuali.ole.coa.businessobject.Account;
027import org.kuali.ole.coa.businessobject.Organization;
028import org.kuali.ole.coa.service.ChartService;
029import org.kuali.ole.coa.service.OrganizationService;
030import org.kuali.ole.sys.OLEConstants.ChartApcParms;
031import org.kuali.ole.sys.OLEPropertyConstants;
032import org.kuali.ole.sys.businessobject.ChartOrgHolderImpl;
033import org.kuali.ole.sys.service.NonTransactional;
034import org.kuali.rice.coreservice.framework.parameter.ParameterService;
035import org.kuali.rice.krad.service.BusinessObjectService;
036import org.springframework.cache.annotation.Cacheable;
037
038/**
039 * This class is the service implementation for the Org structure. This is the default implementation, that is delivered with Kuali.
040 */
041
042@NonTransactional
043public class OrganizationServiceImpl implements OrganizationService {
044    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OrganizationServiceImpl.class);
045    
046    protected ParameterService parameterService;
047    protected ChartService chartService;
048    protected BusinessObjectService boService;
049
050    protected Map<ChartOrgHolderImpl,ChartOrgHolderImpl> parentOrgCache = null;
051
052    /**
053     * 
054     * @see org.kuali.ole.coa.service.OrganizationService#getByPrimaryId(java.lang.String, java.lang.String)
055     */
056    @Override
057    public Organization getByPrimaryId(String chartOfAccountsCode, String organizationCode) {
058        Map<String, Object> keys = new HashMap<String, Object>();
059        keys.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
060        keys.put(OLEPropertyConstants.ORGANIZATION_CODE, organizationCode);
061        return boService.findByPrimaryKey(Organization.class, keys);
062    }
063
064    /**
065     * Implements the getByPrimaryId method defined by OrganizationService. Method is used by KualiOrgReviewAttribute to enable
066     * caching of orgs for routing.
067     * 
068     * @see org.kuali.ole.coa.service.impl.OrganizationServiceImpl#getByPrimaryId(java.lang.String, java.lang.String)
069     */
070    @Override
071    @Cacheable(value=Organization.CACHE_NAME, key="#p0+'-'+#p1")
072    public Organization getByPrimaryIdWithCaching(String chartOfAccountsCode, String organizationCode) {
073        return getByPrimaryId(chartOfAccountsCode, organizationCode);
074    }
075
076    /**
077     * @see org.kuali.ole.coa.service.OrganizationService#getActiveAccountsByOrg(java.lang.String, java.lang.String)
078     */
079    @Override
080    public List<Account> getActiveAccountsByOrg(String chartOfAccountsCode, String organizationCode) {
081
082        if (StringUtils.isBlank(chartOfAccountsCode)) {
083            throw new IllegalArgumentException("String parameter chartOfAccountsCode was null or blank.");
084        }
085        if (StringUtils.isBlank(organizationCode)) {
086            throw new IllegalArgumentException("String parameter organizationCode was null or blank.");
087        }
088        
089        Map<String, Object> criteria = new HashMap<String, Object>();
090        criteria.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
091        criteria.put(OLEPropertyConstants.ORGANIZATION_CODE, organizationCode);
092        criteria.put(OLEPropertyConstants.ACTIVE, Boolean.TRUE);
093        return new ArrayList<Account>( boService.findMatching(Account.class, criteria) );
094    }
095
096    /**
097     * @see org.kuali.ole.coa.service.OrganizationService#getActiveChildOrgs(java.lang.String, java.lang.String)
098     */
099    @Override
100    public List<Organization> getActiveChildOrgs(String chartOfAccountsCode, String organizationCode) {
101        if (StringUtils.isBlank(chartOfAccountsCode)) {
102            throw new IllegalArgumentException("String parameter chartOfAccountsCode was null or blank.");
103        }
104        if (StringUtils.isBlank(organizationCode)) {
105            throw new IllegalArgumentException("String parameter organizationCode was null or blank.");
106        }
107
108        Map<String, Object> criteria = new HashMap<String, Object>();
109        criteria.put(OLEPropertyConstants.REPORTS_TO_CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
110        criteria.put(OLEPropertyConstants.REPORTS_TO_ORGANIZATION_CODE, organizationCode);
111        criteria.put(OLEPropertyConstants.ACTIVE, Boolean.TRUE);
112
113        return new ArrayList<Organization>( boService.findMatching(Organization.class, criteria) );
114    }
115
116    protected void loadParentOrgCache() {
117        LOG.debug( "START - Initializing parent organization cache" );
118        Map<ChartOrgHolderImpl,ChartOrgHolderImpl> temp = new HashMap<ChartOrgHolderImpl, ChartOrgHolderImpl>();
119        
120        Collection<Organization> orgs = boService.findMatching(Organization.class, Collections.singletonMap(OLEPropertyConstants.ACTIVE, true));
121        for ( Organization org : orgs ) {
122            ChartOrgHolderImpl keyOrg = new ChartOrgHolderImpl(org);
123            if ( StringUtils.isNotBlank( org.getReportsToChartOfAccountsCode() ) 
124                    && StringUtils.isNotBlank( org.getReportsToOrganizationCode() ) ) {
125                ChartOrgHolderImpl parentorg = new ChartOrgHolderImpl( org.getReportsToChartOfAccountsCode(), org.getReportsToOrganizationCode());
126                temp.put(keyOrg, parentorg);
127            }
128        }
129        
130        parentOrgCache = temp;
131        if ( LOG.isDebugEnabled() ) {
132            LOG.debug( "COMPLETE - Initializing parent organization cache - " + temp.size() + " organizations loaded" );
133        }
134    }
135    
136    @Override
137    public void flushParentOrgCache() {
138        LOG.debug( "Flushing parent organization cache" );
139        parentOrgCache = null;
140    }
141
142    @Override
143    public boolean isParentOrganization( String childChartOfAccountsCode, String childOrganizationCode, String parentChartOfAccountsCode, String parentOrganizationCode ) {
144        if (StringUtils.isBlank(childChartOfAccountsCode)
145                || StringUtils.isBlank(childOrganizationCode)
146                || StringUtils.isBlank(parentChartOfAccountsCode)
147                || StringUtils.isBlank(parentOrganizationCode) ) {
148            return false;
149        }
150
151        if ( parentOrgCache == null ) {
152            loadParentOrgCache();
153        }
154        
155        ChartOrgHolderImpl currOrg = new ChartOrgHolderImpl( childChartOfAccountsCode, childOrganizationCode );
156        ChartOrgHolderImpl desiredParentOrg = new ChartOrgHolderImpl( parentChartOfAccountsCode, parentOrganizationCode );
157        
158        // the the orgs are the same, we can short circuit the search right now
159        if ( currOrg.equals( desiredParentOrg ) ) {
160            return true;
161        }
162        
163        return isParentOrganization_Internal(currOrg, desiredParentOrg, new ArrayList<ChartOrgHolderImpl>() );
164    }
165
166    /**
167     * This helper method handles the case where there might be cycles in the data.
168     * 
169     */
170    protected boolean isParentOrganization_Internal( ChartOrgHolderImpl currOrg, ChartOrgHolderImpl desiredParentOrg, List<ChartOrgHolderImpl> traversedOrgs ) {
171        
172        if ( traversedOrgs.contains(currOrg) ) {
173            LOG.error( "THERE IS A LOOP IN THE ORG DATA: " + currOrg + " found a second time after traversing the following orgs: " + traversedOrgs );
174            return false;
175        }
176        
177        ChartOrgHolderImpl parentOrg = parentOrgCache.get(currOrg);
178        
179        // we could not find it in the table, return false
180        if ( parentOrg == null ) {
181            return false;
182        }
183        // it is its own parent, then false (we reached the top and did not find a match)
184        if ( parentOrg.equals(currOrg) ) {
185            return false;
186        }
187        // check parent org against desired parent organization
188        if ( parentOrg.equals( desiredParentOrg ) ) {
189            return true;
190        }
191        // otherwise, we don't know yet - so re-call this method moving up to the next parent org 
192        traversedOrgs.add( currOrg );
193        return isParentOrganization_Internal(parentOrg, desiredParentOrg, traversedOrgs);
194    }
195        
196    /**
197     * 
198     * @see org.kuali.ole.coa.service.OrganizationService#getActiveOrgsByType(java.lang.String)
199     */
200    @Override
201    public List<Organization> getActiveOrgsByType(String organizationTypeCode) {
202        if (StringUtils.isBlank(organizationTypeCode)) {
203            throw new IllegalArgumentException("String parameter organizationTypeCode was null or blank.");
204        }
205        Map<String, Object> criteria = new HashMap<String, Object>();
206        criteria.put(OLEPropertyConstants.ORGANIZATION_TYPE_CODE, organizationTypeCode);
207        criteria.put(OLEPropertyConstants.ACTIVE, Boolean.TRUE);
208
209        return new ArrayList<Organization>( boService.findMatching(Organization.class, criteria) );
210    }
211
212    /**
213     * 
214     * @see org.kuali.ole.coa.service.OrganizationService#getActiveFinancialOrgs()
215     */
216    @Override
217    public List<Organization> getActiveFinancialOrgs() {
218        Map<String, Object> criteria = new HashMap<String, Object>();
219        criteria.put(OLEPropertyConstants.ORGANIZATION_IN_FINANCIAL_PROCESSING_INDICATOR, Boolean.TRUE);
220        criteria.put(OLEPropertyConstants.ACTIVE, Boolean.TRUE);
221        return new ArrayList<Organization>( boService.findMatching(Organization.class, criteria) );
222    }
223    
224    /**
225     * 
226     * TODO: refactor me to a ChartOrgHolder
227     * 
228     * @see org.kuali.ole.coa.service.OrganizationService#getRootOrganizationCode()
229     */
230    @Override
231    public String[] getRootOrganizationCode() {
232        String rootChart = chartService.getUniversityChart().getChartOfAccountsCode();
233        String selfReportsOrgType = parameterService.getParameterValueAsString(Organization.class, ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES);
234        String[] returnValues = { null, null };
235        
236        Map<String, Object> criteria = new HashMap<String, Object>();
237        criteria.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, rootChart);
238        criteria.put(OLEPropertyConstants.ORGANIZATION_TYPE_CODE, selfReportsOrgType);
239        criteria.put(OLEPropertyConstants.ACTIVE, Boolean.TRUE);
240        
241        Collection<Organization> results = boService.findMatching(Organization.class, criteria);
242        if (results != null && !results.isEmpty()) {
243            Organization org = results.iterator().next();
244            returnValues[0] = org.getChartOfAccountsCode();
245            returnValues[1] = org.getOrganizationCode();       
246        }
247        
248        return returnValues;
249    }
250
251    public void setParameterService(ParameterService parameterService) {
252        this.parameterService = parameterService;
253    }
254    public void setBusinessObjectService(BusinessObjectService boService) {
255        this.boService = boService;
256    }
257    public void setChartService(ChartService chartService) {
258        this.chartService = chartService;
259    }
260
261}