/* * 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.sys.batch.dataaccess.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.beanutils.PropertyUtils; import org.apache.log4j.Logger; import org.apache.ojb.broker.core.proxy.ProxyHelper; import org.apache.ojb.broker.query.Criteria; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.batch.dataaccess.FiscalYearMaker; import org.kuali.kfs.sys.businessobject.FiscalYearBasedBusinessObject; import org.kuali.kfs.sys.businessobject.SystemOptions; import org.kuali.rice.core.api.mo.common.active.MutableInactivatable; import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.PersistenceStructureService; import org.kuali.rice.krad.util.ObjectUtils; /** * Default implementation of fiscal year maker process for an entity. This implementation can be used for a table in the fiscal year * maker process by defining a spring bean and setting the businessObjectClass property. */ public class FiscalYearMakerImpl extends PlatformAwareDaoBaseOjb implements FiscalYearMaker { private static final Logger LOG = org.apache.log4j.Logger.getLogger(FiscalYearMakerImpl.class); protected static final Long ONE = new Long(1); protected PersistenceStructureService persistenceStructureService; protected BusinessObjectService businessObjectService; protected Class<? extends FiscalYearBasedBusinessObject> businessObjectClass; protected Set<Class<? extends FiscalYearBasedBusinessObject>> parentClasses; protected boolean fiscalYearOneBehind; protected boolean fiscalYearOneAhead; protected boolean twoYearCopy; protected boolean carryForwardInactive; protected boolean allowOverrideTargetYear; protected Boolean hasExtension = null; protected List<String> primaryKeyPropertyNames = null; protected List<String> propertyNames = null; @SuppressWarnings("rawtypes") protected Map<String, Class> referenceObjects = null; @SuppressWarnings("rawtypes") protected Map<String, Class> collectionObjects = null; protected Map<String,Map<String,String>> referenceForeignKeys = new HashMap<String, Map<String,String>>(); /** * Constructs a FiscalYearMakerImpl.java. */ public FiscalYearMakerImpl() { fiscalYearOneBehind = false; fiscalYearOneAhead = false; twoYearCopy = false; carryForwardInactive = false; allowOverrideTargetYear = true; parentClasses = new HashSet<Class<? extends FiscalYearBasedBusinessObject>>(); } protected boolean hasExtension() { if ( hasExtension == null ) { hasExtension = persistenceStructureService.hasReference(businessObjectClass, KFSPropertyConstants.EXTENSION); } return hasExtension.booleanValue(); } @Override public List<String> getPrimaryKeyPropertyNames() { if ( primaryKeyPropertyNames == null ) { primaryKeyPropertyNames = persistenceStructureService.listPrimaryKeyFieldNames(businessObjectClass); } return primaryKeyPropertyNames; } @Override public List<String> getPropertyNames() { if ( propertyNames == null ) { propertyNames = persistenceStructureService.listFieldNames(businessObjectClass); } return propertyNames; } @Override @SuppressWarnings("rawtypes") public Map<String,Class> getReferenceObjectProperties() { if ( referenceObjects == null ) { referenceObjects = persistenceStructureService.listReferenceObjectFields(businessObjectClass); } return referenceObjects; } @Override @SuppressWarnings("rawtypes") public Map<String,Class> getCollectionProperties() { if ( collectionObjects == null ) { collectionObjects = persistenceStructureService.listCollectionObjectTypes(businessObjectClass); } return collectionObjects; } @Override public Map<String,String> getForeignKeyMappings( String referenceName ) { if ( !referenceForeignKeys.containsKey(referenceName) ) { referenceForeignKeys.put(referenceName, persistenceStructureService.getForeignKeysForReference(businessObjectClass, referenceName) ); } return referenceForeignKeys.get(referenceName); } /** * Sets fiscal year field up one, resets version number and assigns a new Guid for the object id * * @see org.kuali.kfs.coa.dataaccess.FiscalYearMaker#changeForNewYear(java.lang.Integer, * org.kuali.rice.krad.bo.PersistableBusinessObject) */ @Override public void changeForNewYear(Integer baseFiscalYear, FiscalYearBasedBusinessObject currentRecord) { if ( LOG.isDebugEnabled() ) { LOG.debug("starting changeForNewYear() for bo class " + businessObjectClass.getName()); } try { // increment fiscal year by 1 Integer newFiscalYear = currentRecord.getUniversityFiscalYear() + 1; // update extension, must be done before updating main record so we can retrieve the extension record by reference updateExtensionRecord(newFiscalYear, currentRecord); // update main record fields currentRecord.setUniversityFiscalYear(newFiscalYear); currentRecord.setVersionNumber(ONE); currentRecord.setObjectId(java.util.UUID.randomUUID().toString()); } catch (Exception e) { String msg = String.format("Failed to set properties for class %s due to %s", businessObjectClass.getName(), e.getMessage()); LOG.error(msg); throw new RuntimeException(msg, e); } } /** * Determines if an extension record is mapped up and exists for the current record. If so then updates the version number, * object id, and clears the primary keys so they will be relinked when storing the main record * * @param newFiscalYear fiscal year to set * @param currentRecord main record with possible extension reference */ protected void updateExtensionRecord(Integer newFiscalYear, PersistableBusinessObject currentRecord) throws Exception { // check if reference is mapped up if ( !hasExtension() ) { return; } // try to retrieve extension record currentRecord.refreshReferenceObject(KFSPropertyConstants.EXTENSION); PersistableBusinessObject extension = currentRecord.getExtension(); // if found then update fields if (ObjectUtils.isNotNull(extension)) { extension = (PersistableBusinessObject)ProxyHelper.getRealObject(extension); extension.setVersionNumber(ONE); extension.setObjectId(java.util.UUID.randomUUID().toString()); // since this could be a new object (no extension object present on the source record) // we need to set the keys // But...we only need to do this if this was a truly new object, which we can tell by checking // the fiscal year field if ( ((FiscalYearBasedBusinessObject)extension).getUniversityFiscalYear() == null ) { for ( String pkField : getPrimaryKeyPropertyNames() ) { PropertyUtils.setSimpleProperty(extension, pkField, PropertyUtils.getSimpleProperty(currentRecord, pkField)); } } ((FiscalYearBasedBusinessObject)extension).setUniversityFiscalYear(newFiscalYear); } } /** * @see org.kuali.rice.core.api.mo.common.active.MutableInactivatable * @see org.kuali.kfs.coa.dataaccess.FiscalYearMaker#createSelectionCriteria(java.lang.Integer) */ @Override public Criteria createNextYearSelectionCriteria(Integer baseFiscalYear) { if ( LOG.isDebugEnabled() ) { LOG.debug("starting createNextYearSelectionCriteria() for bo class " + businessObjectClass.getName()); } Criteria criteria = new Criteria(); addYearCriteria(criteria, baseFiscalYear + 1, twoYearCopy); return criteria; } /** * Selects records for the given base year or base year minus one if this is a lagging copy. If this is a two year copy base * year plus one records will be selected as well. In addition will only select active records if the business object class * implements the MutableInactivatable interface and has the active property. * * @see org.kuali.rice.core.api.mo.common.active.MutableInactivatable * @see org.kuali.kfs.coa.dataaccess.FiscalYearMaker#createSelectionCriteria(java.lang.Integer) */ @Override public Criteria createSelectionCriteria(Integer baseFiscalYear) { if ( LOG.isDebugEnabled() ) { LOG.debug("starting createSelectionCriteria() for bo class " + businessObjectClass.getName()); } Criteria criteria = new Criteria(); addYearCriteria(criteria, baseFiscalYear, false); // add active criteria if the business object class supports the inactivateable interface List<String> fields = getPropertyNames(); if (MutableInactivatable.class.isAssignableFrom(businessObjectClass) && fields.contains(KFSPropertyConstants.ACTIVE) && !carryForwardInactive) { criteria.addEqualTo(KFSPropertyConstants.ACTIVE, KFSConstants.ACTIVE_INDICATOR); } return criteria; } /** * Selects records to delete for base year + 1 (or base year for lagging, and base year + 2 for two year) * * @see org.kuali.kfs.coa.batch.dataaccess.FiscalYearMakerHelper#createDeleteCriteria(java.lang.Integer) */ @Override public Criteria createDeleteCriteria(Integer baseFiscalYear) { if ( LOG.isDebugEnabled() ) { LOG.debug("starting createDeleteCriteria() for bo class " + businessObjectClass.getName()); } Criteria criteria = new Criteria(); addYearCriteria(criteria, baseFiscalYear + 1, twoYearCopy); return criteria; } /** * Adds fiscal year criteria based on the configuration (copy two years, lagging, or normal) * * @param criteria OJB Criteria object * @param baseFiscalYear Fiscal year for critiera * @param createTwoYears indicates whether two years of fiscal year criteria should be added */ protected void addYearCriteria(Criteria criteria, Integer baseFiscalYear, boolean createTwoYears) { if (fiscalYearOneBehind) { baseFiscalYear = baseFiscalYear - 1; } else if (fiscalYearOneAhead) { baseFiscalYear = baseFiscalYear + 1; } if (createTwoYears) { List<Integer> copyYears = new ArrayList<Integer>(); copyYears.add(baseFiscalYear); copyYears.add(baseFiscalYear + 1); criteria.addIn(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, copyYears); } else { criteria.addEqualTo(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, baseFiscalYear); } } /** * Default implementation does nothing * * @see org.kuali.kfs.coa.batch.dataaccess.FiscalYearMakerHelper#performCustomProcessing(java.lang.Integer) */ @Override public void performCustomProcessing(Integer baseFiscalYear, boolean firstCopyYear) { } /** * Default to doing both normal FYM process and custom * * @see org.kuali.kfs.coa.batch.dataaccess.FiscalYearMakerHelper#doCustomProcessingOnly() */ @Override public boolean doCustomProcessingOnly() { return false; } /** * @see org.kuali.kfs.coa.dataaccess.FiscalYearMaker#getBusinessObjectClass() */ @Override public Class<? extends FiscalYearBasedBusinessObject> getBusinessObjectClass() { return businessObjectClass; } /** * <code>Options</code> is the parent for univFiscalYear which all our copy objects should have. Added to list here by default. * * @see org.kuali.kfs.coa.batch.dataaccess.FiscalYearMakerHelper#getParentClasses() * @see org.kuali.kfs.sys.businessobject.Options */ @Override public Set<Class<? extends FiscalYearBasedBusinessObject>> getParentClasses() { if (!businessObjectClass.equals(SystemOptions.class) && !parentClasses.contains(SystemOptions.class)) { parentClasses.add(SystemOptions.class); } return parentClasses; } /** * Sets the businessObjectClass attribute value. * * @param businessObjectClass The businessObjectClass to set. */ public void setBusinessObjectClass(Class<? extends FiscalYearBasedBusinessObject> businessObjectClass) { this.businessObjectClass = businessObjectClass; } /** * Sets the parentClasses attribute value. * * @param parentClasses The parentClasses to set. */ public void setParentClasses(Set<Class<? extends FiscalYearBasedBusinessObject>> parentClasses) { this.parentClasses = parentClasses; } /** * Gets the fiscalYearOneBehind attribute. * * @return Returns the fiscalYearOneBehind. */ public boolean isFiscalYearOneBehind() { return fiscalYearOneBehind; } /** * Sets the fiscalYearOneBehind attribute value. * * @param fiscalYearOneBehind The fiscalYearOneBehind to set. */ public void setFiscalYearOneBehind(boolean fiscalYearOneBehind) { this.fiscalYearOneBehind = fiscalYearOneBehind; } /** * Gets the fiscalYearOneAhead attribute. * * @return Returns the fiscalYearOneAhead. */ public boolean isFiscalYearOneAhead() { return fiscalYearOneAhead; } /** * Sets the fiscalYearOneAhead attribute value. * * @param fiscalYearOneAhead The fiscalYearOneAhead to set. */ public void setFiscalYearOneAhead(boolean fiscalYearOneAhead) { this.fiscalYearOneAhead = fiscalYearOneAhead; } /** * Gets the twoYearCopy attribute. * * @return Returns the twoYearCopy. */ @Override public boolean isTwoYearCopy() { return twoYearCopy; } /** * Sets the twoYearCopy attribute value. * * @param twoYearCopy The twoYearCopy to set. */ public void setTwoYearCopy(boolean twoYearCopy) { this.twoYearCopy = twoYearCopy; } /** * Gets the carryForwardInactive attribute. * * @return Returns the carryForwardInactive. */ public boolean isCarryForwardInactive() { return carryForwardInactive; } /** * Sets the carryForwardInactive attribute value. * * @param carryForwardInactive The carryForwardInactive to set. */ public void setCarryForwardInactive(boolean carryForwardInactive) { this.carryForwardInactive = carryForwardInactive; } /** * Sets the persistenceStructureService attribute value. * * @param persistenceStructureService The persistenceStructureService to set. */ public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) { this.persistenceStructureService = persistenceStructureService; } /** * Gets the allowOverrideTargetYear attribute. * * @return Returns the allowOverrideTargetYear. */ @Override public boolean isAllowOverrideTargetYear() { return allowOverrideTargetYear; } /** * Sets the allowOverrideTargetYear attribute value. * * @param allowOverrideTargetYear The allowOverrideTargetYear to set. */ public void setAllowOverrideTargetYear(boolean allowOverrideTargetYear) { this.allowOverrideTargetYear = allowOverrideTargetYear; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } }