0001: /*
0002: * Copyright 2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.module.chart.dao.ojb;
0017:
0018: import java.lang.reflect.InvocationTargetException;
0019: import java.sql.Date;
0020: import java.util.ArrayList;
0021: import java.util.Calendar;
0022: import java.util.Collection;
0023: import java.util.GregorianCalendar;
0024: import java.util.HashMap;
0025: import java.util.HashSet;
0026: import java.util.Iterator;
0027: import java.util.LinkedHashMap;
0028: import java.util.Map;
0029: import java.util.regex.Matcher;
0030: import java.util.regex.Pattern;
0031:
0032: import org.apache.commons.beanutils.PropertyUtils;
0033: import org.apache.log4j.Logger;
0034: import org.apache.ojb.broker.metadata.ClassDescriptor;
0035: import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
0036: import org.apache.ojb.broker.metadata.CollectionDescriptor;
0037: import org.apache.ojb.broker.metadata.DescriptorRepository;
0038: import org.apache.ojb.broker.metadata.MetadataManager;
0039: import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
0040: import org.apache.ojb.broker.query.Criteria;
0041: import org.apache.ojb.broker.query.QueryByCriteria;
0042: import org.apache.ojb.broker.query.ReportQueryByCriteria;
0043: import org.kuali.core.dao.ojb.PlatformAwareDaoBaseOjb;
0044: import org.kuali.core.service.DateTimeService;
0045: import org.kuali.core.service.PersistenceStructureService;
0046: import org.kuali.core.util.TransactionalServiceUtils;
0047: import org.kuali.kfs.KFSConstants;
0048: import org.kuali.kfs.KFSPropertyConstants;
0049: import org.kuali.kfs.KFSConstants.BudgetConstructionConstants;
0050: import org.kuali.kfs.bo.Options;
0051: import org.kuali.module.chart.bo.AccountingPeriod;
0052: import org.kuali.module.chart.bo.IcrAutomatedEntry;
0053: import org.kuali.module.chart.bo.ObjectCode;
0054: import org.kuali.module.chart.bo.OffsetDefinition;
0055: import org.kuali.module.chart.bo.OrganizationReversion;
0056: import org.kuali.module.chart.bo.OrganizationReversionDetail;
0057: import org.kuali.module.chart.bo.SubObjCd;
0058: import org.kuali.module.chart.dao.FiscalYearMakersCopyAction;
0059: import org.kuali.module.chart.dao.FiscalYearMakersDao;
0060: import org.kuali.module.chart.dao.FiscalYearMakersFieldChangeAction;
0061: import org.kuali.module.chart.dao.FiscalYearMakersFilterAction;
0062: import org.kuali.module.gl.bo.UniversityDate;
0063: import org.kuali.module.labor.bo.BenefitsCalculation;
0064: import org.kuali.module.labor.bo.LaborObject;
0065: import org.kuali.module.labor.bo.PositionObjectBenefit;
0066:
0067: /**
0068: * This class...
0069: */
0070: public class FiscalYearMakersDaoOjb extends PlatformAwareDaoBaseOjb
0071: implements FiscalYearMakersDao {
0072:
0073: /*
0074: * These routines are designed to create rows for the next fiscal year for reference tables, based on the rows in those tables
0075: * for the current fiscal year. The idea is to relieve people of the responsibility for typing in hundreds of new rows in a
0076: * maintenance document, and to preclude having to auto-generate reference rows for x years in the future, maintaining them as
0077: * things change. There are two modes used by routines in this module. (1) slash-and-burn: if any rows for the target year
0078: * exist, they are deleted, and replaced with copies of the current year's rows (2) warm-and-fuzzy: any rows for the new year
0079: * that already exist are left in place, and only those rows whose keys are missing in the new fiscal year are copied from the
0080: * current year There are two versions of each method (using overloading). To get the slash-and_burn version, one uses the
0081: * method where there is a second parameter, and passes its value as the static variable "replaceMode".
0082: */
0083:
0084: /* turn on the logger for the persistence broker */
0085: private static Logger LOG = org.apache.log4j.Logger
0086: .getLogger(FiscalYearMakersDaoOjb.class);
0087:
0088: private DateTimeService dateTimeService;
0089: private PersistenceStructureService persistenceStructureService;
0090:
0091: private UniversityDate universityDate;
0092:
0093: /*
0094: * these fields are used for setting up the UniversityDate data they are here because RI in the database requires that
0095: * UniversityDate be treated just like any other table in fiscal year makers there will be one start for each month the code
0096: * will adjust the year from the reference year these values are intended ONLY to reset the fiscal year beginning date
0097: */
0098: private Integer BeginYear;
0099: private String FiscalYear;
0100: private static final Integer ReferenceYear = 1900;
0101: public static final GregorianCalendar START_JANUARY = new GregorianCalendar(
0102: ReferenceYear, Calendar.JANUARY, 1);
0103: public static final GregorianCalendar START_FEBRUARY = new GregorianCalendar(
0104: ReferenceYear, Calendar.FEBRUARY, 1);
0105: public static final GregorianCalendar START_MARCH = new GregorianCalendar(
0106: ReferenceYear, Calendar.MARCH, 1);
0107: public static final GregorianCalendar START_APRIL = new GregorianCalendar(
0108: ReferenceYear, Calendar.APRIL, 1);
0109: public static final GregorianCalendar START_MAY = new GregorianCalendar(
0110: ReferenceYear, Calendar.MAY, 1);
0111: public static final GregorianCalendar START_JUNE = new GregorianCalendar(
0112: ReferenceYear, Calendar.JUNE, 1);
0113: public static final GregorianCalendar START_JULY = new GregorianCalendar(
0114: ReferenceYear, Calendar.JULY, 1);
0115: public static final GregorianCalendar START_AUGUST = new GregorianCalendar(
0116: ReferenceYear, Calendar.AUGUST, 1);
0117: public static final GregorianCalendar START_SEPTEMBER = new GregorianCalendar(
0118: ReferenceYear, Calendar.SEPTEMBER, 1);
0119: public static final GregorianCalendar START_OCTOBER = new GregorianCalendar(
0120: ReferenceYear, Calendar.OCTOBER, 1);
0121: public static final GregorianCalendar START_NOVEMBER = new GregorianCalendar(
0122: ReferenceYear, Calendar.NOVEMBER, 1);
0123: public static final GregorianCalendar START_DECEMBER = new GregorianCalendar(
0124: ReferenceYear, Calendar.DECEMBER, 1);
0125: private GregorianCalendar fiscalYearStartDate;
0126:
0127: public static final boolean replaceMode = true;
0128:
0129: /**
0130: * delete all the rows (if any) for the request year for all the classes in the ordered delete list the delete order is set so
0131: * that referential integrity will not cause an exception: children first, then parents
0132: *
0133: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#deleteNewYearRows(java.lang.Integer)
0134: */
0135: public void deleteNewYearRows(Integer requestYear) {
0136:
0137: for (Map.Entry<String, Class> classesToDelete : getDeleteOrder()
0138: .entrySet()) {
0139: Integer RequestYear = requestYear;
0140: if (laggingCopyCycle.contains(classesToDelete.getKey())) {
0141: // this object is copied from LAST PERIOD into the CURRENT PERIOD
0142: RequestYear = RequestYear - 1;
0143: }
0144: deleteNewYearRows(RequestYear, classesToDelete.getValue());
0145: }
0146: getPersistenceBrokerTemplate().clearCache();
0147: }
0148:
0149: /**
0150: * this routine gets rid of existing rows for the request year + 1 for the parents of the child passed as a parameter it is uses
0151: * when, for some classes, we want to create two years' worth of rows on each run
0152: *
0153: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#deleteYearAfterNewYearRowsForParents(java.lang.Integer, java.lang.Class)
0154: */
0155: public void deleteYearAfterNewYearRowsForParents(
0156: Integer RequestYear, Class childClass) {
0157: RequestYear = RequestYear + 1;
0158: // first we have to delete the child rows
0159: deleteNewYearRows(RequestYear, childClass);
0160: // now we loop through the delete order, and delete each parent in turn
0161: for (Map.Entry<String, Class> classesToDelete : getDeleteOrder()
0162: .entrySet()) {
0163: // better compare the names just to be safe
0164: Class deleteClass = classesToDelete.getValue();
0165: if (isAParentOf(deleteClass.getName(), childClass)) {
0166: deleteNewYearRows(RequestYear, deleteClass);
0167: }
0168: }
0169: }
0170:
0171: /**
0172: * This method checks to see if a given child class is a parent of another class (denoted by a String)
0173: *
0174: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#isAParentOf(java.lang.String, java.lang.Class)
0175: */
0176: public boolean isAParentOf(String testClassName, Class childClass) {
0177: ArrayList<Class> parentClasses = childParentMap.get(childClass
0178: .getName());
0179: Iterator<Class> parents = parentClasses.iterator();
0180: // we compare names to be safe
0181: while (parents.hasNext()) {
0182: if (testClassName.compareTo(parents.next().getName()) == 0) {
0183: return true;
0184: }
0185: }
0186: return false;
0187: }
0188:
0189: /**
0190: * this is the routine where you designate which objects should participate and whether they should use customized field setters
0191: * or customized query filters the objects participating MUST match the object list configured in the XML
0192: *
0193: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#setUpRun(java.lang.Integer, boolean)
0194: */
0195: public LinkedHashMap<String, FiscalYearMakersCopyAction> setUpRun(
0196: Integer BaseYear, boolean replaceMode) {
0197:
0198: // added October, 2007, to remove all OJB auto-update and auto-delete codes
0199: // which would alter the delete and copy order set by the XML-encoded parent-child relationships
0200: // this code changes the settings in memory for this run only
0201: // this implies that fiscal year makers should run in its own container, with no
0202: // other jobs which might depend on the auto-xxx settings.
0203: turnOffCascades();
0204:
0205: /***************************************************************************************************************************
0206: * AccountingPeriod *
0207: **************************************************************************************************************************/
0208: FiscalYearMakersCopyAction copyActionAcctPrd = new FiscalYearMakersCopyAction() {
0209: FiscalYearMakersFieldChangeAction<AccountingPeriod> fieldAction = new FiscalYearMakersFieldChangeAction<AccountingPeriod>() {
0210: public void customFieldChangeMethod(
0211: Integer currentFiscalYear,
0212: Integer newFiscalYear,
0213: AccountingPeriod candidateRow) {
0214: // there is a four-character year in periods 01 through 12, and a two-character
0215: // year in period 13. we need to update these for the new year. instead of
0216: // hard-wiring in the periods, we just try to make both changes
0217: Integer startThisYear = currentFiscalYear - 1;
0218: String startThisYearString = startThisYear
0219: .toString();
0220: String currentFiscalYearString = currentFiscalYear
0221: .toString();
0222: String newFiscalYearString = newFiscalYear
0223: .toString();
0224: String nameString = candidateRow
0225: .getUniversityFiscalPeriodName();
0226: candidateRow
0227: .setUniversityFiscalPeriodName(updateStringField(
0228: newFiscalYearString,
0229: currentFiscalYearString,
0230: candidateRow
0231: .getUniversityFiscalPeriodName()));
0232: candidateRow
0233: .setUniversityFiscalPeriodName(updateStringField(
0234: currentFiscalYearString,
0235: startThisYearString,
0236: candidateRow
0237: .getUniversityFiscalPeriodName()));
0238: candidateRow
0239: .setUniversityFiscalPeriodName(updateTwoDigitYear(
0240: newFiscalYearString.substring(2, 4),
0241: currentFiscalYearString.substring(
0242: 2, 4),
0243: candidateRow
0244: .getUniversityFiscalPeriodName()));
0245: candidateRow
0246: .setUniversityFiscalPeriodName(updateTwoDigitYear(
0247: currentFiscalYearString.substring(
0248: 2, 4),
0249: startThisYearString.substring(2, 4),
0250: candidateRow
0251: .getUniversityFiscalPeriodName()));
0252: // we have to update the ending date, increasing it by one year
0253: candidateRow
0254: .setUniversityFiscalPeriodEndDate(addYearToDate(candidateRow
0255: .getUniversityFiscalPeriodEndDate()));
0256: // we set all of the fiscal period status codes to "closed" before the
0257: // start of the coming year
0258: candidateRow
0259: .setUniversityFiscalPeriodStatusCode(KFSConstants.ACCOUNTING_PERIOD_STATUS_OPEN);
0260: }
0261: };
0262:
0263: public void copyMethod(Integer baseYear, boolean replaceMode) {
0264: MakersMethods<AccountingPeriod> makersMethod = new MakersMethods<AccountingPeriod>();
0265: makersMethod.makeMethod(AccountingPeriod.class,
0266: baseYear, replaceMode, fieldAction);
0267: }
0268: };
0269: addCopyAction(AccountingPeriod.class, copyActionAcctPrd);
0270:
0271: /***************************************************************************************************************************
0272: * BenefitsCalculation *
0273: **************************************************************************************************************************/
0274: FiscalYearMakersCopyAction copyActionBenCalc = new FiscalYearMakersCopyAction() {
0275: public void copyMethod(Integer baseYear, boolean replaceMode) {
0276: MakersMethods<BenefitsCalculation> makersMethod = new MakersMethods<BenefitsCalculation>();
0277: makersMethod.makeMethod(BenefitsCalculation.class,
0278: baseYear, replaceMode);
0279: }
0280: };
0281: addCopyAction(BenefitsCalculation.class, copyActionBenCalc);
0282:
0283: /***************************************************************************************************************************
0284: * IcrAutomatedEntry *
0285: **************************************************************************************************************************/
0286: FiscalYearMakersCopyAction copyActionIcrAuto = new FiscalYearMakersCopyAction() {
0287: public void copyMethod(Integer baseYear, boolean replaceMode) {
0288: MakersMethods<IcrAutomatedEntry> makersMethod = new MakersMethods<IcrAutomatedEntry>();
0289: makersMethod.makeMethod(IcrAutomatedEntry.class,
0290: baseYear, replaceMode);
0291: }
0292: };
0293: addCopyAction(IcrAutomatedEntry.class, copyActionIcrAuto);
0294:
0295: /***************************************************************************************************************************
0296: * LaborObject *
0297: **************************************************************************************************************************/
0298: FiscalYearMakersCopyAction copyActionLabObj = new FiscalYearMakersCopyAction() {
0299: public void copyMethod(Integer baseYear, boolean replaceMode) {
0300: MakersMethods<LaborObject> makersMethod = new MakersMethods<LaborObject>();
0301: makersMethod.makeMethod(LaborObject.class, baseYear,
0302: replaceMode);
0303: }
0304: };
0305: addCopyAction(LaborObject.class, copyActionLabObj);
0306:
0307: /***************************************************************************************************************************
0308: * ObjectCode *
0309: **************************************************************************************************************************/
0310: FiscalYearMakersCopyAction copyActionObjectCode = new FiscalYearMakersCopyAction() {
0311: FiscalYearMakersFilterAction filterObjectCode = new FiscalYearMakersFilterAction() {
0312: public Criteria customCriteriaMethod() {
0313: // this method allows us to add any filters needed on the current
0314: // year rows--for example, we might not want any marked deleted.
0315: // for ObjectCode, we don't want any invalid objects--UNLESS they
0316: // are the dummy object used in budget construction
0317: Criteria criteriaID = new Criteria();
0318: criteriaID
0319: .addEqualTo(
0320: KFSPropertyConstants.FINANCIAL_OBJECT_ACTIVE_CODE,
0321: true);
0322: Criteria criteriaBdg = new Criteria();
0323: criteriaBdg
0324: .addEqualTo(
0325: KFSPropertyConstants.FINANCIAL_OBJECT_CODE,
0326: BudgetConstructionConstants.OBJECT_CODE_2PLG);
0327: criteriaID.addOrCriteria(criteriaBdg);
0328: return criteriaID;
0329: }
0330: };
0331:
0332: public void copyMethod(Integer baseYear, boolean replaceMode) {
0333: MakersMethods<ObjectCode> makersMethod = new MakersMethods<ObjectCode>();
0334: makersMethod.makeMethod(ObjectCode.class, baseYear,
0335: replaceMode, filterObjectCode);
0336: }
0337: };
0338: addCopyAction(ObjectCode.class, copyActionObjectCode);
0339:
0340: /***************************************************************************************************************************
0341: * OffsetDefinition *
0342: **************************************************************************************************************************/
0343: FiscalYearMakersCopyAction copyActionOffDef = new FiscalYearMakersCopyAction() {
0344: public void copyMethod(Integer baseYear, boolean replaceMode) {
0345: MakersMethods<OffsetDefinition> makersMethod = new MakersMethods<OffsetDefinition>();
0346: makersMethod.makeMethod(OffsetDefinition.class,
0347: baseYear, replaceMode);
0348: }
0349: };
0350: addCopyAction(OffsetDefinition.class, copyActionOffDef);
0351:
0352: /***************************************************************************************************************************
0353: * Options *
0354: **************************************************************************************************************************/
0355: FiscalYearMakersCopyAction copyActionOptions = new FiscalYearMakersCopyAction() {
0356: FiscalYearMakersFieldChangeAction<Options> fieldAction = new FiscalYearMakersFieldChangeAction<Options>() {
0357: public void customFieldChangeMethod(
0358: Integer currentFiscalYear,
0359: Integer newFiscalYear, Options candidateRow) {
0360: // some ineffeciency in set up is traded for easier maintenance
0361: Integer currentYearStart = currentFiscalYear - 1;
0362: String endNextYearString = newFiscalYear.toString();
0363: String endCurrentYearString = currentFiscalYear
0364: .toString();
0365: String startCurrentYearString = currentYearStart
0366: .toString();
0367: candidateRow
0368: .setUniversityFiscalYearStartYr(currentFiscalYear);
0369: // here we allow for a substring of XXXX-YYYY as well as XXXX and YYYY
0370: String holdIt = updateStringField(
0371: endNextYearString, endCurrentYearString,
0372: candidateRow.getUniversityFiscalYearName());
0373: candidateRow
0374: .setUniversityFiscalYearName(updateStringField(
0375: endCurrentYearString,
0376: startCurrentYearString, holdIt));
0377: }
0378: };
0379:
0380: public void copyMethod(Integer baseYear, boolean replaceMode) {
0381: MakersMethods<Options> makersMethod = new MakersMethods<Options>();
0382: makersMethod.makeMethod(Options.class, baseYear,
0383: replaceMode, fieldAction);
0384: }
0385: };
0386: addCopyAction(Options.class, copyActionOptions);
0387:
0388: /***************************************************************************************************************************
0389: * OrganizationReversion *
0390: **************************************************************************************************************************/
0391: FiscalYearMakersCopyAction copyActionOrgRev = new FiscalYearMakersCopyAction() {
0392: public void copyMethod(Integer baseYear, boolean replaceMode) {
0393: MakersMethods<OrganizationReversion> makersMethod = new MakersMethods<OrganizationReversion>();
0394: makersMethod.makeMethod(OrganizationReversion.class,
0395: baseYear, replaceMode);
0396: }
0397: };
0398: addCopyAction(OrganizationReversion.class, copyActionOrgRev);
0399:
0400: /***************************************************************************************************************************
0401: * OrganizationReversionDetail *
0402: **************************************************************************************************************************/
0403: FiscalYearMakersCopyAction copyActionOrgRevDtl = new FiscalYearMakersCopyAction() {
0404: public void copyMethod(Integer baseYear, boolean replaceMode) {
0405: MakersMethods<OrganizationReversionDetail> makersMethod = new MakersMethods<OrganizationReversionDetail>();
0406: makersMethod.makeMethod(
0407: OrganizationReversionDetail.class, baseYear,
0408: replaceMode);
0409: }
0410: };
0411: addCopyAction(OrganizationReversionDetail.class,
0412: copyActionOrgRevDtl);
0413:
0414: /***************************************************************************************************************************
0415: * PositionObjectBenefit *
0416: **************************************************************************************************************************/
0417: FiscalYearMakersCopyAction copyActionPosObjBen = new FiscalYearMakersCopyAction() {
0418: public void copyMethod(Integer baseYear, boolean replaceMode) {
0419: MakersMethods<PositionObjectBenefit> makersMethod = new MakersMethods<PositionObjectBenefit>();
0420: makersMethod.makeMethod(PositionObjectBenefit.class,
0421: baseYear, replaceMode);
0422: }
0423: };
0424: addCopyAction(PositionObjectBenefit.class, copyActionPosObjBen);
0425:
0426: /***************************************************************************************************************************
0427: * SubObjCd *
0428: **************************************************************************************************************************/
0429: FiscalYearMakersCopyAction copyActionSubObjCd = new FiscalYearMakersCopyAction() {
0430: /*
0431: * not for phase II FiscalYearMakersFilterAction filterSubObjectCode = new FiscalYearMakersFilterAction() { public
0432: * Criteria customCriteriaMethod() { // this method allows us to add any filters needed on the current // year rows--for
0433: * example, we might not want any marked deleted. // for SubObjectCode, we don't want any invalid objects Criteria
0434: * criteriaID = new Criteria(); criteriaID.addEqualTo(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_ACTIVE_INDICATOR,true);
0435: * return criteriaID; } };
0436: */
0437: public void copyMethod(Integer baseYear, boolean replaceMode) {
0438: MakersMethods<SubObjCd> makersMethod = new MakersMethods<SubObjCd>();
0439: makersMethod.makeMethod(SubObjCd.class, baseYear,
0440: replaceMode);
0441: /*
0442: * not for phase II replaceMode, filterSubObjectCode);
0443: */
0444: }
0445: };
0446: addCopyAction(SubObjCd.class, copyActionSubObjCd);
0447: /***************************************************************************************************************************
0448: * University Date *
0449: **************************************************************************************************************************/
0450: FiscalYearMakersCopyAction copyActionUniversityDate = new FiscalYearMakersCopyAction() {
0451: public void copyMethod(Integer currentFiscalYear,
0452: boolean replaceMode) {
0453: // this is the routine to call to build the new year's university date tables
0454: // the start month of the fiscal year is passed in with the reference date
0455: // the year in the reference date is updated to the year of the current fiscal
0456: // year
0457: // if we start on January 1, the year of the beginning date should in fact be
0458: // the year FOLLOWING the currentFiscalYear. Otherwise, the first date of the
0459: // new fiscal year falls within the year in which the current fiscal year ends.
0460: if (fiscalYearStartDate.equals(START_JANUARY)) {
0461: currentFiscalYear = currentFiscalYear + 1;
0462: }
0463: GregorianCalendar newYearStartDate = new GregorianCalendar(
0464: fiscalYearStartDate.get(Calendar.YEAR),
0465: fiscalYearStartDate.get(Calendar.MONTH),
0466: fiscalYearStartDate.get(Calendar.DAY_OF_MONTH));
0467: int yearDifference = currentFiscalYear
0468: - newYearStartDate.get(Calendar.YEAR);
0469: newYearStartDate.add(Calendar.YEAR, yearDifference);
0470: makeUniversityDate(newYearStartDate);
0471: }
0472: };
0473: addCopyAction(UniversityDate.class, copyActionUniversityDate);
0474: /** ***************************************************************** */
0475: //
0476: // this is the routine that sets up and and returns the runtime call order
0477: // addCopyAction must have been called on every object to be copied
0478: // before this routine is called
0479: return (getCopyOrder());
0480: }
0481:
0482: /*******************************************************************************************************************************
0483: * University Date Database Access *
0484: ******************************************************************************************************************************/
0485:
0486: /**
0487: * this is the only routine that simply replaces what is there, if anything but, we have to do a delete--otherwise, we can get
0488: * an optimistic locking exception when we try to store a new row on top of something already in the database. we will delete by
0489: * fiscal year. the accounting period is assumed to correspond to the month, with the month of the start date being the first
0490: * period and the month of the last day of the fiscal year being the twelfth. the fiscal year tag is always the year of the
0491: * ending date of the fiscal year
0492: *
0493: * @param fiscalYearStartDate
0494: */
0495: public void makeUniversityDate(GregorianCalendar fiscalYearStartDate) {
0496: // loop through a year's worth of dates for the new year
0497: GregorianCalendar shunivdate = new GregorianCalendar(
0498: fiscalYearStartDate.get(Calendar.YEAR),
0499: fiscalYearStartDate.get(Calendar.MONTH),
0500: fiscalYearStartDate.get(Calendar.DAY_OF_MONTH));
0501: // set up the end date
0502: GregorianCalendar enddate = new GregorianCalendar(
0503: fiscalYearStartDate.get(Calendar.YEAR),
0504: fiscalYearStartDate.get(Calendar.MONTH),
0505: fiscalYearStartDate.get(Calendar.DAY_OF_MONTH));
0506: enddate.add(Calendar.MONTH, 12);
0507: enddate.add(Calendar.DAY_OF_MONTH, -1);
0508: // the fiscal year is always the year of the ending date of the fiscal year
0509: Integer nextFiscalYear = (Integer) enddate.get(Calendar.YEAR);
0510: // get rid of anything already there
0511: deleteNewYearRows(nextFiscalYear, UniversityDate.class);
0512: // initialize the period variables
0513: int period = 1;
0514: String periodString = String.format("%02d", period);
0515: int compareMonth = shunivdate.get(Calendar.MONTH);
0516: int currentMonth = shunivdate.get(Calendar.MONTH);
0517: // loop through the dates until we hit the last one
0518: while (!(shunivdate.equals(enddate))) {
0519: // TODO: temporary debugging code
0520: LOG.debug(String.format("\n%s %s %tD:%tT", nextFiscalYear,
0521: periodString, shunivdate, shunivdate));
0522: // store these values--we will update whatever is there
0523: UniversityDate universityDate = new UniversityDate();
0524: universityDate.setUniversityFiscalYear(nextFiscalYear);
0525: universityDate.setUniversityDate(new Date(shunivdate
0526: .getTimeInMillis()));
0527: universityDate
0528: .setUniversityFiscalAccountingPeriod(periodString);
0529: getPersistenceBrokerTemplate().store(universityDate);
0530: // next day
0531: shunivdate.add(Calendar.DAY_OF_MONTH, 1);
0532: // does this kick us into a new month and therefore a new accounting period?
0533: compareMonth = shunivdate.get(Calendar.MONTH);
0534: if (currentMonth != compareMonth) {
0535: period = period + 1;
0536: periodString = String.format("%02d", period);
0537: currentMonth = compareMonth;
0538: // TODO: debugging code
0539: if (period == 13) {
0540: LOG
0541: .warn("the date comparison is not working properly");
0542: break;
0543: }
0544: }
0545: }
0546: // store the end date
0547: UniversityDate universityDate = new UniversityDate();
0548: universityDate.setUniversityFiscalYear(nextFiscalYear);
0549: universityDate.setUniversityDate(new Date(shunivdate
0550: .getTimeInMillis()));
0551: universityDate
0552: .setUniversityFiscalAccountingPeriod(periodString);
0553: getPersistenceBrokerTemplate().store(universityDate);
0554: // TODO: temporary debugging code
0555: LOG.debug(String.format("\n%s %s %tD:%tT\n", nextFiscalYear,
0556: periodString, shunivdate, shunivdate));
0557: }
0558:
0559: /**
0560: * This method is a private utility method to perform some date operations
0561: *
0562: * @param inDate
0563: * @return date with one year added
0564: */
0565: private java.sql.Date addYearToDate(Date inDate) {
0566: // OK. Apparently the JDK is trying to offer a generic calendar to all
0567: // users. java.sql.Date (which extends java.util.Date) is trying to create
0568: // a DB independent date value. both have settled on milliseconds since
0569: // midnight, January 1, 1970, Greenwich Mean Time. This value is then
0570: // "normalized" to the local time zone when it is converted to a date. But,
0571: // the constructors are based on the original millisecond value, and this
0572: // value is recoverable via the "time" methods in the classes.
0573: GregorianCalendar currentCalendarDate = new GregorianCalendar();
0574: // create a calendar object with no values set
0575: currentCalendarDate.clear();
0576: // set the calendar values using the "standard" millisecond value
0577: // this should represent the java.sql.Date value in the local time zone
0578: currentCalendarDate.setTimeInMillis(inDate.getTime());
0579: // add a year to the SQL date
0580: currentCalendarDate.add(GregorianCalendar.YEAR, 1);
0581: // return the "standardized" value of the orginal date + 1 year
0582: return (new Date(currentCalendarDate.getTimeInMillis()));
0583: }
0584:
0585: /**
0586: * This method builds and returns a hash set containing the composite key string of rows that already exist in the relevant
0587: * table for the new fiscal year (we assume all the members of the composite key are strings except the fiscal year.
0588: *
0589: * @param requestYear
0590: * @param businessObject
0591: * @return a hash set with the composite key string of rows
0592: */
0593: private HashSet<String> buildMapOfExistingKeys(Integer requestYear,
0594: Class businessObject) {
0595:
0596: Criteria criteriaID = new Criteria();
0597: criteriaID.addEqualTo(
0598: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
0599: requestYear);
0600: // get space for the map
0601: HashSet<String> returnHash = new HashSet<String>(
0602: hashObjectSize(businessObject, criteriaID));
0603: // set up to query for the key fields
0604: String[] attrib = { "" }; // we'll reorient this pointer when we know the size
0605: attrib = (String[]) persistenceStructureService.getPrimaryKeys(
0606: businessObject).toArray(attrib);
0607: ReportQueryByCriteria queryID = new ReportQueryByCriteria(
0608: businessObject, attrib, criteriaID);
0609: Iterator keyValues = getPersistenceBrokerTemplate()
0610: .getReportQueryIteratorByQuery(queryID);
0611: while (keyValues.hasNext()) {
0612: Object[] keyObject = (Object[]) keyValues.next();
0613: // we assume the fiscal year is an integer, and the other keys are strings
0614: // OJB always returns BigDecimal for a number (including fiscal year),
0615: // but we apply toString directly to the object, so we should be OK.
0616: StringBuffer concatKey = new StringBuffer(keyObject[0]
0617: .toString());
0618: for (int i = 1; i < keyObject.length; i++) {
0619: concatKey = concatKey.append(keyObject[i]);
0620: }
0621: returnHash.add(concatKey.toString());
0622: }
0623: return returnHash;
0624: }
0625:
0626: /**
0627: * This method gets rid of all the rows in the new fiscal year
0628: *
0629: * @param requestYear
0630: * @param businessObject
0631: */
0632: private void deleteNewYearRows(Integer requestYear,
0633: Class businessObject) {
0634: LOG.warn(String.format("\ndeleting %s for %d", businessObject
0635: .getName(), requestYear));
0636: Criteria criteriaID = new Criteria();
0637: criteriaID.addEqualTo(
0638: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
0639: requestYear);
0640: QueryByCriteria queryID = new QueryByCriteria(businessObject,
0641: criteriaID);
0642: getPersistenceBrokerTemplate().deleteByQuery(queryID);
0643: LOG.warn(String.format("\n rows for %d deleted", requestYear));
0644: getPersistenceBrokerTemplate().clearCache();
0645: }
0646:
0647: /**
0648: * we look up the fiscal year for today's date, and return it we return 0 if nothing is found
0649: *
0650: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#fiscalYearFromToday()
0651: */
0652: public Integer fiscalYearFromToday() {
0653:
0654: Integer currentFiscalYear = new Integer(0);
0655: Date lookUpDate = dateTimeService.getCurrentSqlDateMidnight();
0656: Criteria criteriaID = new Criteria();
0657: criteriaID.addEqualTo(KFSPropertyConstants.UNIVERSITY_DATE,
0658: lookUpDate);
0659: String[] attrb = { KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR };
0660: ReportQueryByCriteria queryID = new ReportQueryByCriteria(
0661: UniversityDate.class, attrb, criteriaID);
0662: Iterator resultRow = getPersistenceBrokerTemplate()
0663: .getReportQueryIteratorByQuery(queryID);
0664: if (resultRow.hasNext()) {
0665: currentFiscalYear = (Integer) ((Number) ((Object[]) TransactionalServiceUtils
0666: .retrieveFirstAndExhaustIterator(resultRow))[0])
0667: .intValue();
0668: }
0669: // TODO:
0670: LOG.debug(String.format(
0671: "\nreturned from fiscalYearFromToday: %d",
0672: currentFiscalYear));
0673: // TODO:
0674: return currentFiscalYear;
0675: }
0676:
0677: // @@TODO:
0678: // this code is duplicated from GenesisDaoOjb. we don't need to overload the
0679: // hashObjectSize method here
0680: // if this thing catches on, maybe we should make the hashObjectSize method
0681: // a public method in a service
0682: //
0683: /**
0684: * This method determines a hash capacity given by a hash size this corresponds to a little more than the default load factor of
0685: * .75 a rehash supposedly occurs when the actual number of elements exceeds (load factor)*capacity N rows < .75 capacity ==>
0686: * capacity > 4N/3 or 1.3333N. We add a little slop.
0687: *
0688: * @param hashSize
0689: * @return recommended hash capacity based on hash size
0690: */
0691: private Integer hashCapacity(Integer hashSize) {
0692:
0693: Double tempValue = hashSize.floatValue() * (1.45);
0694: return (Integer) tempValue.intValue();
0695: }
0696:
0697: /**
0698: * This method calculates a given hash set size based on objects retrieved or 1 if no objects
0699: *
0700: * @param classID
0701: * @param criteriaID
0702: * @return hash set size
0703: */
0704: private Integer hashObjectSize(Class classID, Criteria criteriaID) {
0705: // this counts all rows
0706: String[] selectList = new String[] { "COUNT(*)" };
0707: ReportQueryByCriteria queryID = new ReportQueryByCriteria(
0708: classID, selectList, criteriaID);
0709: Iterator resultRows = getPersistenceBrokerTemplate()
0710: .getReportQueryIteratorByQuery(queryID);
0711: while (resultRows.hasNext()) {
0712: return (hashCapacity(((Number) ((Object[]) TransactionalServiceUtils
0713: .retrieveFirstAndExhaustIterator(resultRows))[0])
0714: .intValue()));
0715: }
0716: return (new Integer(1));
0717: }
0718:
0719: /**
0720: * this routine is reminiscent of computing in 1970, when disk space was scarce and every byte was fraught with meaning. some
0721: * fields are captions and titles, and they contain things like the fiscal year. for the new year, we have to update these
0722: * substrings in place, so they don't have to be updated by hand to display correct information in the application. we use the
0723: * regular expression utilities in java
0724: *
0725: * @param newYearString
0726: * @param oldYearString
0727: * @param currentField
0728: * @return the updated string
0729: */
0730: private String updateStringField(String newYearString,
0731: String oldYearString, String currentField) {
0732: Pattern pattern = Pattern.compile(oldYearString);
0733: Matcher matcher = pattern.matcher(currentField);
0734: return matcher.replaceAll(newYearString);
0735: }
0736:
0737: /**
0738: * this routine is provided to update string fields which contain two-digit years that need to be updated for display. it is
0739: * very specific, but it's necessary. "two-digit year" means the two numeric characters preceded by a non-numeric character.
0740: *
0741: * @param newYear
0742: * @param oldYear
0743: * @param currentString
0744: * @return the updated string for a two digit year
0745: */
0746: private String updateTwoDigitYear(String newYear, String oldYear,
0747: String currentString) {
0748: // group 1 is the bounded by the outermost set of parentheses
0749: // group 2 is the first inner set
0750: // group 3 is the second inner set--a two-digit year at the beginning of the line
0751: String regExpString = "(([^0-9]{1}" + oldYear + ")|^("
0752: + oldYear + "))";
0753: Pattern pattern = Pattern.compile(regExpString);
0754: Matcher matcher = pattern.matcher(currentString);
0755: // start looking for a match
0756: boolean matched = matcher.find();
0757: if (!matched) {
0758: // just return if nothing is found
0759: return currentString;
0760: }
0761: // we found something
0762: // we have to process it
0763: String returnString = currentString;
0764: StringBuffer outString = new StringBuffer();
0765: // is there a match at the beginning of the line (a match with group 3)?
0766: if (matcher.group(3) != null) {
0767: // there is a two-digit-year string at the beginning of the line
0768: // we want to replace it
0769: matcher.appendReplacement(outString, newYear);
0770: // find the next match if there is one
0771: matched = matcher.find();
0772: }
0773: while (matched) {
0774: // the new string will no longer match with group 3
0775: // if there is still a match, it will be with group 2
0776: // now we have to prefix the new year string with the same
0777: // non-numeric character as the next match (hyphen, space, whatever)
0778: String newYearString = matcher.group(2).substring(0, 1)
0779: + newYear;
0780: matcher.appendReplacement(outString, newYearString);
0781: matched = matcher.find();
0782: }
0783: // dump whatever detritus is left into the new string
0784: matcher.appendTail(outString);
0785: return outString.toString();
0786: }
0787:
0788: public void setDateTimeService(DateTimeService dateTimeService) {
0789: this .dateTimeService = dateTimeService;
0790: }
0791:
0792: public void setPersistenceStructureService(
0793: PersistenceStructureService persistenceStructureService) {
0794: this .persistenceStructureService = persistenceStructureService;
0795: }
0796:
0797: public void setFiscalYearStartDate(
0798: GregorianCalendar fiscalYearStartDate) {
0799: // this routine can be used to reset the default start date for the
0800: // fiscal year
0801: this .fiscalYearStartDate = fiscalYearStartDate;
0802: }
0803:
0804: /**
0805: * This class is a generic class to pass in types to the generic routines
0806: */
0807: private class MakersMethods<T> {
0808: // this is the signature used for an object that requires no
0809: // special processing
0810: private void makeMethod(Class ojbMappedClass,
0811: Integer currentFiscalYear, boolean replaceMode) {
0812: FiscalYearMakersFieldChangeAction changeAction = null;
0813: FiscalYearMakersFilterAction filterAction = null;
0814: makeMethod(ojbMappedClass, currentFiscalYear, replaceMode,
0815: changeAction, filterAction);
0816: }
0817:
0818: // this is the signature used for an object which has special
0819: // filter criteria in the WHERE clause
0820: private void makeMethod(Class ojbMappedClass,
0821: Integer currentFiscalYear, boolean replaceMode,
0822: FiscalYearMakersFilterAction filterAction) {
0823: FiscalYearMakersFieldChangeAction changeAction = null;
0824: makeMethod(ojbMappedClass, currentFiscalYear, replaceMode,
0825: changeAction, filterAction);
0826: }
0827:
0828: // this is the signature used for an object which requires changes
0829: // to fields other than KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR
0830: private void makeMethod(Class ojbMappedClass,
0831: Integer currentFiscalYear, boolean replaceMode,
0832: FiscalYearMakersFieldChangeAction changeAction) {
0833: FiscalYearMakersFilterAction filterAction = null;
0834: makeMethod(ojbMappedClass, currentFiscalYear, replaceMode,
0835: changeAction, filterAction);
0836: }
0837:
0838: /**
0839: * this is the signature used for an object which has both special filter criteria and required changes to additional fields
0840: *
0841: * @param ojbMappedClass
0842: * @param currentFiscalYear
0843: * @param replaceMode
0844: * @param changeAction
0845: * @param filterAction
0846: */
0847: private void makeMethod(Class ojbMappedClass,
0848: Integer currentFiscalYear, boolean replaceMode,
0849: FiscalYearMakersFieldChangeAction changeAction,
0850: FiscalYearMakersFilterAction filterAction) {
0851: if (laggingCopyCycle.contains(ojbMappedClass.getName())) {
0852: // this object is copied from LAST PERIOD into the CURRENT PERIOD
0853: currentFiscalYear = currentFiscalYear - 1;
0854: }
0855: if (replaceMode) {
0856: // we will replace any new year rows that exist
0857: try {
0858: genericSlashAndBurn(ojbMappedClass,
0859: currentFiscalYear, changeAction,
0860: filterAction);
0861: } catch (IllegalAccessException ex) {
0862: ex.printStackTrace();
0863: RuntimeException newEx = new RuntimeException(
0864: String.format("\n failed for %s",
0865: ojbMappedClass.getName()));
0866: throw (newEx);
0867: } catch (InvocationTargetException ex) {
0868: ex.printStackTrace();
0869: RuntimeException newEx = new RuntimeException(
0870: String.format("\n failed for %s",
0871: ojbMappedClass.getName()));
0872: throw (newEx);
0873: } catch (NoSuchFieldException ex) {
0874: ex.printStackTrace();
0875: RuntimeException newEx = new RuntimeException(
0876: String.format("\n failed for %s",
0877: ojbMappedClass.getName()));
0878: throw (newEx);
0879: } catch (NoSuchMethodException ex) {
0880: ex.printStackTrace();
0881: RuntimeException newEx = new RuntimeException(
0882: String.format("\n failed for %s",
0883: ojbMappedClass.getName()));
0884: throw (newEx);
0885: }
0886: } else {
0887: // we will only add any new year rows that do not already exist
0888: try {
0889: genericWarmAndFuzzy(ojbMappedClass,
0890: currentFiscalYear, changeAction,
0891: filterAction);
0892: } catch (IllegalAccessException ex) {
0893: ex.printStackTrace();
0894: RuntimeException newEx = new RuntimeException(
0895: String.format("\n failed for %s",
0896: ojbMappedClass.getName()));
0897: throw (newEx);
0898: } catch (InvocationTargetException ex) {
0899: ex.printStackTrace();
0900: RuntimeException newEx = new RuntimeException(
0901: String.format("\n failed for %s",
0902: ojbMappedClass.getName()));
0903: throw (newEx);
0904: } catch (NoSuchFieldException ex) {
0905: ex.printStackTrace();
0906: RuntimeException newEx = new RuntimeException(
0907: String.format("\n failed for %s",
0908: ojbMappedClass.getName()));
0909: throw (newEx);
0910: } catch (NoSuchMethodException ex) {
0911: ex.printStackTrace();
0912: RuntimeException newEx = new RuntimeException(
0913: String.format("\n failed for %s",
0914: ojbMappedClass.getName()));
0915: throw (newEx);
0916: }
0917: }
0918: }
0919:
0920: /**
0921: * routine to build rows for the coming fiscal year, replacing any that already exist
0922: *
0923: * @param ojbMappedClass
0924: * @param currentFiscalYear
0925: * @param changeAction
0926: * @param filterAction
0927: * @throws NoSuchFieldException
0928: * @throws NoSuchMethodException
0929: * @throws IllegalAccessException
0930: * @throws InvocationTargetException
0931: */
0932: private void genericSlashAndBurn(Class ojbMappedClass,
0933: Integer currentFiscalYear,
0934: FiscalYearMakersFieldChangeAction changeAction,
0935: FiscalYearMakersFilterAction filterAction)
0936: throws NoSuchFieldException, NoSuchMethodException,
0937: IllegalAccessException, InvocationTargetException {
0938: Integer newFiscalYear = currentFiscalYear + 1;
0939: String requestYearString = newFiscalYear.toString();
0940: Integer rowsRead = new Integer(0);
0941: Integer rowsWritten = new Integer(0);
0942: Integer rowsFailingRI = new Integer(0);
0943: LOG.warn(String.format("\n copying %s from %d to %d",
0944: ojbMappedClass.getName(), currentFiscalYear,
0945: newFiscalYear));
0946: // some parents have auto-update other than none in their relationship
0947: // with a child. in this case, the child's rows were written when the
0948: // parent's rows were written, and writing the child's rows again now will
0949: // cause an optimistic locking exception. we have already deleted any rows
0950: // the existed for the target year, so any rows there now have been written
0951: // by an auto-update, are therefore correct, and should not be recopied.
0952: // we check here for the existence of rows.
0953: Criteria checkCriteria = new Criteria();
0954: checkCriteria.addEqualTo(
0955: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
0956: newFiscalYear);
0957: if (hashObjectSize(ojbMappedClass, checkCriteria) > 0) {
0958: LOG
0959: .warn(String
0960: .format(
0961: "\n %s rows for %d exist already from an auto-update",
0962: ojbMappedClass.getName(),
0963: newFiscalYear));
0964: return;
0965: }
0966: // build the list of parent keys already copied to the new year (if any)
0967: // the appropriate child foreign keys must exist in each parent
0968: // if they do not, this means that the parent row in the current fiscal
0969: // year was filtered out in the copy to the new fiscal year, and therefore
0970: // the corresponding child rows should not be copied either
0971: ParentKeyChecker<T> parentKeyChecker = new ParentKeyChecker<T>(
0972: ojbMappedClass, newFiscalYear);
0973: // get the rows from the previous year
0974: Criteria criteriaID = new Criteria();
0975: criteriaID.addEqualTo(
0976: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
0977: currentFiscalYear);
0978: if (filterAction != null) {
0979: criteriaID.addAndCriteria(filterAction
0980: .customCriteriaMethod());
0981: }
0982: QueryByCriteria queryID = new QueryByCriteria(
0983: ojbMappedClass, criteriaID);
0984: Iterator<T> oldYearObjects = getPersistenceBrokerTemplate()
0985: .getIteratorByQuery(queryID);
0986: while (oldYearObjects.hasNext()) {
0987: rowsRead = rowsRead + 1;
0988: T ourBO = oldYearObjects.next();
0989: // we have to set the fiscal year and the version number
0990: setCommonFields(ourBO, newFiscalYear);
0991: // we also set all the custom fields (which presumably are NOT
0992: // keys). they may be foreign keys into the parent, and if they
0993: // are the parent check should reflect the new year values.
0994: if (!(changeAction == null)) {
0995: changeAction.customFieldChangeMethod(
0996: currentFiscalYear, newFiscalYear, ourBO);
0997: }
0998: // check to see if the row exists in all the parents
0999: if (!parentKeyChecker.childRowSatisfiesRI(ourBO)) {
1000: rowsFailingRI = rowsFailingRI + 1;
1001: continue;
1002: }
1003: // store the result
1004: getPersistenceBrokerTemplate().store(ourBO);
1005: rowsWritten = rowsWritten + 1;
1006: }
1007: LOG
1008: .warn(String
1009: .format(
1010: "\n%s:\n%d read = %d\n%d written = %d\nfailed RI = %d",
1011: ojbMappedClass.getName(),
1012: currentFiscalYear, rowsRead,
1013: newFiscalYear, rowsWritten,
1014: rowsFailingRI));
1015: // if a parent has inverse foreign keys into the child, when we copy the
1016: // parent the child rows will be in the cache as well of auto-retrieve is
1017: // true and proxy is false. then, when we build the child rows later these
1018: // cached rows cause an OJB "optimistic locking" error. we therefore remove
1019: // any cached rows after all the rows for each object have been copied to
1020: // the database
1021: getPersistenceBrokerTemplate().clearCache();
1022: }
1023:
1024: /**
1025: * routine to only add, not replace, rows for the coming fiscal year
1026: *
1027: * @param ojbMappedClass
1028: * @param currentFiscalYear
1029: * @param changeAction
1030: * @param filterAction
1031: * @throws NoSuchFieldException
1032: * @throws NoSuchMethodException
1033: * @throws IllegalAccessException
1034: * @throws InvocationTargetException
1035: */
1036: private void genericWarmAndFuzzy(Class ojbMappedClass,
1037: Integer currentFiscalYear,
1038: FiscalYearMakersFieldChangeAction changeAction,
1039: FiscalYearMakersFilterAction filterAction)
1040: throws NoSuchFieldException, NoSuchMethodException,
1041: IllegalAccessException, InvocationTargetException {
1042: Integer newFiscalYear = currentFiscalYear + 1;
1043: String requestYearString = newFiscalYear.toString();
1044: Integer rowsRead = new Integer(0);
1045: Integer rowsWritten = new Integer(0);
1046: Integer rowsFailingRI = new Integer(0);
1047: LOG.warn(String.format("\n copying %s from %d to %d",
1048: ojbMappedClass.getName(), currentFiscalYear,
1049: newFiscalYear));
1050: // build the list of parent keys already copied to the new year (if any)
1051: // the appropriate child foreign keys must exist in each parent
1052: // if they do not, this means that the parent row in the current fiscal
1053: // year was filtered out in the copy to the new fiscal year, and therefore
1054: // the corresponding child rows should not be copied either
1055: ParentKeyChecker<T> parentKeyChecker = new ParentKeyChecker<T>(
1056: ojbMappedClass, newFiscalYear);
1057: // get the hash set of keys of objects which already exist for the new
1058: // year and will not be replaced
1059: HashSet existingKeys = buildMapOfExistingKeys(
1060: newFiscalYear, ojbMappedClass);
1061: //
1062: String[] keyFields = { "" }; // reorient this pointer when we know the size
1063: keyFields = (String[]) persistenceStructureService
1064: .getPrimaryKeys(ojbMappedClass).toArray(keyFields);
1065: // get the rows from the previous year
1066: Criteria criteriaID = new Criteria();
1067: criteriaID.addEqualTo(
1068: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
1069: currentFiscalYear);
1070: if (filterAction != null) {
1071: criteriaID.addAndCriteria(filterAction
1072: .customCriteriaMethod());
1073: }
1074: QueryByCriteria queryID = new QueryByCriteria(
1075: ojbMappedClass, criteriaID);
1076: Iterator<T> oldYearObjects = getPersistenceBrokerTemplate()
1077: .getIteratorByQuery(queryID);
1078: while (oldYearObjects.hasNext()) {
1079: rowsRead = rowsRead + 1;
1080: StringBuffer hashChecker = new StringBuffer(
1081: requestYearString);
1082: T ourBO = oldYearObjects.next();
1083: for (int i = 1; i < keyFields.length; i++) {
1084: // 10/2007 some primary keys may not be strings:
1085: // we assume that the same value of a non-string will be converted to a string consistently
1086: hashChecker.append(PropertyUtils.getSimpleProperty(
1087: ourBO, keyFields[i].toString()));
1088: }
1089: // TODO:
1090: // if (rowsRead%1007 == 1)
1091: // {
1092: // LOG.warn(String.format("\n%s: row %d hash key = %s\n",
1093: // ojbMappedClass.getName(),
1094: // rowsRead,hashChecker.toString()));
1095: // }
1096: // TODO:
1097: if (existingKeys.contains(hashChecker.toString())) {
1098: continue;
1099: }
1100: // we have to set the fiscal year and the version number
1101: setCommonFields(ourBO, newFiscalYear);
1102: // we also set all the custom fields (which presumably are NOT
1103: // keys). they may be foreign keys into the parent, and if they
1104: // are the parent check should reflect the new year values.
1105: if (!(changeAction == null)) {
1106: changeAction.customFieldChangeMethod(
1107: currentFiscalYear, newFiscalYear, ourBO);
1108: }
1109: // check to see if the row exists in all the parents
1110: if (!parentKeyChecker.childRowSatisfiesRI(ourBO)) {
1111: rowsFailingRI = rowsFailingRI + 1;
1112: continue;
1113: }
1114: // store the result
1115: getPersistenceBrokerTemplate().store(ourBO);
1116: rowsWritten = rowsWritten + 1;
1117: }
1118: LOG
1119: .warn(String
1120: .format(
1121: "\n%s:\n%d read = %d\n%d written = %d\nfailed RI = %d",
1122: ojbMappedClass.getName(),
1123: currentFiscalYear, rowsRead,
1124: newFiscalYear, rowsWritten,
1125: rowsFailingRI));
1126: // if a parent has inverse foreign keys into the child, when we copy the
1127: // parent the child rows will be in the cache as well of auto-retrieve is
1128: // true and proxy is false. then, when we build the child rows later these
1129: // cached rows cause an OJB "optimistic locking" error. we therefore remove
1130: // any cached rows after all the rows for each object have been copied to
1131: // the database
1132: getPersistenceBrokerTemplate().clearCache();
1133: }
1134:
1135: /**
1136: * the compiler doesn't know the class of the object at this point, so we can't use the methods contained in the object the
1137: * compiler would not be able to find them PropertyUtils uses reflection at run time to find the correct set method
1138: *
1139: * @param ourBO
1140: * @param newFiscalYear
1141: * @throws NoSuchFieldException
1142: * @throws IllegalAccessException
1143: * @throws NoSuchMethodException
1144: * @throws InvocationTargetException
1145: */
1146: private void setCommonFields(T ourBO, Integer newFiscalYear)
1147: throws NoSuchFieldException, IllegalAccessException,
1148: NoSuchMethodException, InvocationTargetException {
1149:
1150: // set the fiscal year
1151: PropertyUtils.setSimpleProperty(ourBO,
1152: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
1153: newFiscalYear);
1154: // set the version number (to avoid running up the meter as the
1155: // years fly by)
1156: // the version number is a field of the base class common to all
1157: // persistable business objects
1158: PropertyUtils.setSimpleProperty(ourBO,
1159: KFSPropertyConstants.VERSION_NUMBER, new Long(0));
1160: // fld =
1161: // ourBO.getClass().getSuperclass().getDeclaredField(KFSPropertyConstants.VERSION_NUMBER);
1162: // fld.setAccessible(true);
1163: // fld.set(ourBO, (Object) (new Long(0)));
1164: }
1165:
1166: };
1167:
1168: /*******************************************************************************************************************************
1169: * This section handles RI *
1170: ******************************************************************************************************************************/
1171:
1172: // list of objects (from XML) that need to be copied for a new fiscal period
1173: private HashMap<String, Class> makerObjectsList = new HashMap<String, Class>(
1174: 25);
1175: // list of objects that need to be copied coupled with a list of
1176: // other objects to be copied on which they have an RI dependency
1177: private HashMap<String, ArrayList<Class>> childParentMap = new HashMap<String, ArrayList<Class>>(
1178: 25);
1179: // this is the source of the above map. it is loaded from XML through a set
1180: // method. if there is no RI, initializing it here should make it empty.
1181: // since the code works for things that have no parents, the code should
1182: // still work if there is no RI and everybody is an orphan
1183: private HashMap<String, Class[]> childParentArrayMap = new HashMap<String, Class[]>(
1184: 25);
1185: // this is the list of copy actions, one for each object to be copied
1186: // it is built in the setUp method
1187: private HashMap<String, FiscalYearMakersCopyAction> classSpecificActions;
1188: // this is the set of objects which are copied from LAST YEAR to the
1189: // current year, instead of from the CURRENT YEAR to the next year
1190: private HashSet<String> laggingCopyCycle = new HashSet<String>(20);
1191:
1192: /**
1193: * the list of all the fiscal year makers objects
1194: *
1195: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#getMakerObjectsList()
1196: */
1197: public HashMap<String, Class> getMakerObjectsList() {
1198: return this .makerObjectsList;
1199: }
1200:
1201: /**
1202: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#setMakerObjectsList(java.util.HashMap)
1203: */
1204: public void setMakerObjectsList(
1205: HashMap<String, Class> makerObjectsList) {
1206: this .makerObjectsList = makerObjectsList;
1207: classSpecificActions = new HashMap<String, FiscalYearMakersCopyAction>(
1208: makerObjectsList.size());
1209: }
1210:
1211: /**
1212: * this list of child/parent relationships for the fiscal year makers objects
1213: *
1214: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#getChildParentMap()
1215: */
1216: public HashMap<String, ArrayList<Class>> getChildParentMap() {
1217: return this .childParentMap;
1218: }
1219:
1220: /**
1221: * Spring did not do the conversions of the XML necessary to create HashMap<String,ArrayList<Class>>. (We got an ArrayList of
1222: * strings.) Since everything was written for an ArrayList, we will convert the Class[] version (which Spring can handle) to an
1223: * ArrayList here. (There is a way to get a "list" view of an array, and this view is an ArrayList. But we will create a new
1224: * one, which will be extensible, unlike the view.)
1225: *
1226: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#setChildParentArrayMap(java.util.HashMap)
1227: */
1228: public void setChildParentArrayMap(
1229: HashMap<String, Class[]> childParentArrayMap) {
1230: this .childParentArrayMap = childParentArrayMap;
1231:
1232: childParentMap = new HashMap<String, ArrayList<Class>>(
1233: childParentArrayMap.size());
1234: for (Map.Entry<String, Class[]> fromMap : childParentArrayMap
1235: .entrySet()) {
1236: Class[] sourceArray = fromMap.getValue();
1237: ArrayList<Class> targetList = new ArrayList<Class>(
1238: sourceArray.length);
1239: for (int i = 0; i < sourceArray.length; i++) {
1240: targetList.add(i, sourceArray[i]);
1241: }
1242: childParentMap.put(fromMap.getKey(), targetList);
1243: }
1244: }
1245:
1246: /**
1247: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#setLaggingCopyCycle(java.util.HashSet)
1248: */
1249: public void setLaggingCopyCycle(HashSet<String> laggingCopyCycle) {
1250: this .laggingCopyCycle = laggingCopyCycle;
1251: }
1252:
1253: /**
1254: * this is a map of the execution order for the classes the class name is followed by a class ID which contains the copy method
1255: * it is called at the end of setUp, and uses the classSpecificActions map built during setUp to get the copy action required
1256: *
1257: * @return
1258: * @throws RuntimeException
1259: */
1260: private LinkedHashMap<String, FiscalYearMakersCopyAction> getCopyOrder()
1261: throws RuntimeException {
1262: // throw an exception if the lists don't match
1263: if (findChildParentXMLErrors()) {
1264: RuntimeException ex = new RuntimeException(
1265: "\nXML class list and parent-child list are incompatible");
1266: throw (ex);
1267: }
1268: LinkedHashMap<String, FiscalYearMakersCopyAction> returnMap = new LinkedHashMap<String, FiscalYearMakersCopyAction>(
1269: makerObjectsList.size());
1270: // make a copy of the child/parent map so we can iterate until all the
1271: // elements in the copy have been added to the copy order list.
1272: // we have to do a deep copy, not just a clone
1273: HashMap<String, ArrayList<Class>> childParentWkngMap = new HashMap<String, ArrayList<Class>>(
1274: childParentMap.size());
1275: for (Map.Entry<String, ArrayList<Class>> sourceMap : childParentMap
1276: .entrySet()) {
1277: ArrayList<Class> srceList = sourceMap.getValue();
1278: ArrayList<Class> targetList = new ArrayList<Class>(srceList
1279: .size());
1280: Iterator<Class> copyIt = srceList.iterator();
1281: while (copyIt.hasNext()) {
1282: targetList.add(copyIt.next());
1283: }
1284: childParentWkngMap.put(sourceMap.getKey(), targetList);
1285: }
1286: // first, add to the list all makers objects which aren't children of anything
1287: for (Map.Entry<String, Class> orphans : makerObjectsList
1288: .entrySet()) {
1289: if (!childParentWkngMap.containsKey(orphans.getKey())) {
1290: // the object is supposed to be a call back action
1291: // we will fill it in later
1292: // for now, we set it to null
1293: returnMap.put(orphans.getKey(), classSpecificActions
1294: .get(orphans.getKey()));
1295: }
1296: }
1297: // since a child of parent X should not be a parent of a parent of
1298: // parent X, this should work. if at any pass through the loop
1299: // we don't add to the copy order, there has to be a circular
1300: // relationship, and we will trow an exception
1301: Integer numberAdded = new Integer(1);
1302: // we need to keep track of parentChildMap elements to be removed
1303: // we cannot do that in the for loop without a "concurrent modification exception"
1304: HashSet<String> removeList = new HashSet<String>(
1305: childParentWkngMap.size());
1306: while (!childParentWkngMap.isEmpty()) {
1307: // nothing was found on the last pass, so nothing will be found on
1308: // any of the subsequent passes either
1309: if (numberAdded.intValue() == 0) {
1310: RuntimeException ex = new RuntimeException(
1311: "child/parent XML map contains circular relationships");
1312: throw (ex);
1313: }
1314: numberAdded = 0;
1315: for (Map.Entry<String, ArrayList<Class>> parentList : childParentWkngMap
1316: .entrySet()) {
1317: Iterator<Class> parents = parentList.getValue()
1318: .iterator();
1319: if (!parents.hasNext()) {
1320: // all the parents have been added
1321: // add the child to the returnMap and delete this entry
1322: numberAdded = numberAdded + 1;
1323: // we will detect a null copy action later and throw an exception
1324: returnMap.put(parentList.getKey(),
1325: classSpecificActions.get(parentList
1326: .getKey()));
1327: removeList.add(parentList.getKey());
1328: continue;
1329: }
1330: // loop trough the parent list and try to add the parents to the
1331: // copy order map
1332: while (parents.hasNext()) {
1333: Class nextParent = parents.next();
1334: // check to see whether this parent is a child of someone else
1335: if ((!childParentWkngMap.containsKey(nextParent
1336: .getName()))
1337: || (removeList.contains(nextParent
1338: .getName()))) {
1339: // we will detect a null copy action later and throw an exception
1340: returnMap.put(nextParent.getName(),
1341: classSpecificActions.get(nextParent
1342: .getName()));
1343: numberAdded = numberAdded + 1;
1344: parents.remove();
1345: }
1346: }
1347: }
1348: // now we want to remove the parents whose children are already
1349: // in the delete list from the parentChildWkngMap
1350: Iterator<String> goners = removeList.iterator();
1351: while (goners.hasNext()) {
1352: childParentWkngMap.remove(goners.next());
1353: }
1354: removeList.clear();
1355: }
1356: // now we have to verify that everything in the map has a valid copy action
1357: boolean screwedUpCopyOrder = false;
1358: for (Map.Entry<String, FiscalYearMakersCopyAction> copyActions : returnMap
1359: .entrySet()) {
1360: if (copyActions.getValue() == null) {
1361: LOG.error(String.format(
1362: "\n%s has no valid copy action", copyActions
1363: .getKey()));
1364: screwedUpCopyOrder = true;
1365: }
1366: }
1367: if (screwedUpCopyOrder) {
1368: RuntimeException rex = new RuntimeException(
1369: "\ninvalid copy order list");
1370: throw (rex);
1371: }
1372: return returnMap;
1373: }
1374:
1375: /**
1376: * this list specifies the delete order for the objects in the list
1377: *
1378: * @return
1379: * @throws RuntimeException
1380: */
1381: private LinkedHashMap<String, Class> getDeleteOrder()
1382: throws RuntimeException {
1383: // throw an exception if the lists don't match
1384: if (findChildParentXMLErrors()) {
1385: RuntimeException ex = new RuntimeException(
1386: "\nXML class list and parent-child list are incompatible");
1387: throw (ex);
1388: }
1389: // this should only be called once
1390: // we'll rebuild it on each call
1391: // first we need a parent child map
1392: HashMap<String, ArrayList<Class>> parentChildMap = createParentChildMap();
1393: // children must be deleted before parents
1394: LinkedHashMap<String, Class> returnList = new LinkedHashMap<String, Class>(
1395: makerObjectsList.size());
1396: // first, add to the list all makers objects which aren't parents of anything
1397: for (Map.Entry<String, Class> childless : makerObjectsList
1398: .entrySet()) {
1399: if (!parentChildMap.containsKey(childless.getKey())) {
1400: returnList
1401: .put(childless.getKey(), childless.getValue());
1402: }
1403: }
1404: // since a child of parent X cannot be a parent to a parent of X,
1405: // this loop should work. we continue until the list is empty
1406: // if there is a pass in which nothing is written, this implies a
1407: // circular relationship and we will throw an exception
1408: Integer numberAdded = new Integer(1);
1409: // we need to keep track of parentChildMap elements to be removed
1410: // we cannot do that in the for loop without a "concurrent modification exception"
1411: HashSet<String> removeList = new HashSet<String>(parentChildMap
1412: .size());
1413: while (!parentChildMap.isEmpty()) {
1414: // if the last loop didn't do anything, then the
1415: // next pass won't either. it means the parent/child map has a circular
1416: // relationship
1417: if (numberAdded.intValue() == 0) {
1418: RuntimeException ex = new RuntimeException(
1419: "child/parent XML map contains circular relationships");
1420: throw (ex);
1421: }
1422: // do another iteration over the shrunken list, and see what else
1423: // we can stick in the delete order list
1424: numberAdded = 0;
1425: for (Map.Entry<String, ArrayList<Class>> parents : parentChildMap
1426: .entrySet()) {
1427: // try to add the children to the return list of classes
1428: Iterator<Class> children = parents.getValue()
1429: .iterator();
1430: // if there are no children, we have already added them all
1431: // it is safe to simply add the parent to the map and remove the row
1432: if (!children.hasNext()) {
1433: numberAdded = numberAdded + 1;
1434: returnList.put(parents.getKey(), makerObjectsList
1435: .get(parents.getKey()));
1436: // add this to the remove list
1437: removeList.add(parents.getKey());
1438: continue;
1439: }
1440: while (children.hasNext()) {
1441: Class nextChild = children.next();
1442: // if the child is already in the map, remove the child
1443: if (returnList.containsKey(nextChild.getName())) {
1444: numberAdded = numberAdded + 1;
1445: children.remove();
1446: continue;
1447: }
1448: // if the child is not a parent of anything still in the list
1449: // we add it to the list and remove it
1450: if ((!parentChildMap.containsKey(nextChild
1451: .getName()))
1452: || (removeList
1453: .contains(nextChild.getName()))) {
1454: numberAdded = numberAdded + 1;
1455: returnList.put(nextChild.getName(), nextChild);
1456: children.remove();
1457: }
1458: }
1459: }
1460: // now we want to remove the parents whose children are already
1461: // in the delete list from the parentChildMap
1462: Iterator<String> goners = removeList.iterator();
1463: while (goners.hasNext()) {
1464: parentChildMap.remove(goners.next());
1465: }
1466: removeList.clear();
1467: }
1468: return returnList;
1469: }
1470:
1471: /**
1472: * this is an "action", or callback, class it allows us to build an instance at run time for each child, after the parents have
1473: * already been built for the coming fiscal period (1) for each parent, store the values that exist for the child's foreign keys
1474: * (2) provide a method that can be called by each child row read from the base period. the method will check that the child has
1475: * the proper RI relationship with at least one row from each parent.
1476: */
1477: public class ParentKeyChecker<C> {
1478: private ParentClass<C>[] parentClassList = null;
1479:
1480: /**
1481: * Constructs a FiscalYearMakersDaoOjb.java.
1482: *
1483: * @param childClass
1484: * @param requestYear
1485: */
1486: public ParentKeyChecker(Class childClass, Integer requestYear) {
1487: String testString = childClass.getName();
1488: if (childParentMap.containsKey(testString)) {
1489: ArrayList<Class> parentClasses = childParentMap
1490: .get(testString);
1491: parentClassList = (ParentClass<C>[]) new ParentClass[parentClasses
1492: .size()];
1493: for (int i = 0; i < parentClasses.size(); i++) {
1494: parentClassList[i] = new ParentClass<C>(
1495: parentClasses.get(i), childClass,
1496: requestYear);
1497: }
1498: }
1499: }
1500:
1501: /**
1502: * This method...
1503: *
1504: * @param ourBO
1505: * @return true if child row satisfies referential integrity
1506: * @throws IllegalAccessException
1507: * @throws InvocationTargetException
1508: * @throws NoSuchMethodException
1509: */
1510: public boolean childRowSatisfiesRI(C ourBO)
1511: throws IllegalAccessException,
1512: InvocationTargetException, NoSuchMethodException {
1513: boolean returnValue = true;
1514: if (parentClassList == null) {
1515: return returnValue;
1516: }
1517: for (int i = 0; i < parentClassList.length; i++) {
1518: returnValue = returnValue
1519: && parentClassList[i].isInParent(ourBO);
1520: }
1521: return returnValue;
1522: }
1523: };
1524:
1525: /**
1526: * this class is used to construct a parent key hashmap, and provide a method to verify that a business object of type C matches
1527: * on its foreign key fields with the parent
1528: */
1529: public class ParentClass<C> {
1530: private String[] childKeyFields;
1531: private String[] parentKeyFields;
1532: private HashSet<String> parentKeys = new HashSet<String>(1);
1533:
1534: /**
1535: * Constructs a FiscalYearMakersDaoOjb.java. the constructor will initialize the key hashmap for this parent object it will
1536: * also get the foreign key fields from the persistence data structure (the assumption is that the fields names returned are
1537: * the same in both the parent class and the child class). try to set this up so that if the parent/child relationship does
1538: * not exist in OJB, we can issue a warning message and go on, and all the methods will still behave properly
1539: *
1540: * @param parentClass
1541: * @param childClass
1542: * @param requestYear
1543: */
1544: public ParentClass(Class parentClass, Class childClass,
1545: Integer requestYear) {
1546: // fill in the key field names
1547: // TODO: fix this--we need the child class as well as the parentClass
1548: ReturnedPair<String[], String[]> keyArrays = fetchForeignKeysToParent(
1549: childClass, parentClass);
1550: childKeyFields = keyArrays.getFirst();
1551: parentKeyFields = keyArrays.getSecond();
1552: if (childKeyFields != null) {
1553: // build a query to get the keys already added to the parent
1554: Criteria criteriaID = new Criteria();
1555: criteriaID.addEqualTo(KFSConstants.UNIV_FISCAL_YR,
1556: requestYear);
1557: ReportQueryByCriteria queryID = new ReportQueryByCriteria(
1558: parentClass, parentKeyFields, criteriaID, true);
1559: // build a hash set of the keys in the parent
1560: parentKeys = new HashSet<String>(hashCapacity(queryID));
1561: Iterator parentRows = getPersistenceBrokerTemplate()
1562: .getReportQueryIteratorByQuery(queryID);
1563: while (parentRows.hasNext()) {
1564: parentKeys.add(buildKeyString((Object[]) parentRows
1565: .next()));
1566: }
1567: }
1568: }
1569:
1570: /**
1571: * This method...
1572: *
1573: * @param ourBO
1574: * @return
1575: * @throws IllegalAccessException
1576: * @throws InvocationTargetException
1577: * @throws NoSuchMethodException
1578: */
1579: private String buildChildTestKey(C ourBO)
1580: throws IllegalAccessException,
1581: InvocationTargetException, NoSuchMethodException {
1582: StringBuffer returnKey = new StringBuffer("");
1583: // we will convert all the keys to strings
1584: for (int i = 0; i < childKeyFields.length; i++) {
1585: returnKey.append(PropertyUtils.getProperty(ourBO,
1586: childKeyFields[i].toString()));
1587: }
1588: return returnKey.toString();
1589: }
1590:
1591: /**
1592: * method to test whether a key of the child row matches one in parent
1593: *
1594: * @param ourBO
1595: * @return
1596: * @throws IllegalAccessException
1597: * @throws InvocationTargetException
1598: * @throws NoSuchMethodException
1599: */
1600: public boolean isInParent(C ourBO)
1601: throws IllegalAccessException,
1602: InvocationTargetException, NoSuchMethodException {
1603: if (childKeyFields == null) {
1604: return false;
1605: }
1606: return (parentKeys.contains(buildChildTestKey(ourBO)));
1607: }
1608:
1609: }
1610:
1611: /*******************************************************************************************************************************
1612: * private methods
1613: ******************************************************************************************************************************/
1614:
1615: /**
1616: * This method takes an object to copy and a copy action and places it in our action list
1617: *
1618: * @param objectToCopy
1619: * @param copyAction
1620: */
1621: private void addCopyAction(Class objectToCopy,
1622: FiscalYearMakersCopyAction copyAction) {
1623: classSpecificActions.put(objectToCopy.getName(), copyAction);
1624: }
1625:
1626: /**
1627: * This method... we always assume the first key is the fiscal year OJB returns a BigDecimal for this (it's a numeric field, and
1628: * in some databases--notably Oracle--every numeric field is stored as a number)
1629: *
1630: * @param inKeys
1631: * @return a string of keys from the array
1632: */
1633: private String buildKeyString(Object[] inKeys) {
1634: StringBuffer stringBuilder = new StringBuffer();
1635:
1636: // stringBuilder.append(((Integer)((BigDecimal) inKeys[0]).intValue()).toString());
1637: // for (int i = 1; i < inKeys.length; i++)
1638: // {
1639: // stringBuilder.append((String)inKeys[i]);
1640: // }
1641: //
1642: // when the parent rows were cached (not yet written to the data base), the
1643: // loop commented out above gave a java.lang.String cast exception.
1644: // this seems to indicate that BigDecimal is returned only from Oracle, and
1645: // not from cached objects. we therefore made the assumption that toString
1646: // will work properly for every type of field we are likely to encounter
1647: for (int i = 0; i < inKeys.length; i++) {
1648: stringBuilder.append(inKeys[i].toString());
1649: }
1650: return stringBuilder.toString();
1651: }
1652:
1653: /**
1654: * This method creates a HashMap of parent->child relationships
1655: *
1656: * @return HashMap that contains a parent key with children as an array
1657: */
1658: private HashMap<String, ArrayList<Class>> createParentChildMap() {
1659: HashMap<String, ArrayList<Class>> returnMap = new HashMap<String, ArrayList<Class>>(
1660: makerObjectsList.size());
1661: // we've checked that every child has a list of unique parents
1662: // so, all we have to do is read the list of children and reverse
1663: // the map
1664: for (Map.Entry<String, ArrayList<Class>> childMap : childParentMap
1665: .entrySet()) {
1666: String childName = childMap.getKey();
1667: Class childClass = makerObjectsList.get(childName);
1668: ArrayList parentArray = childMap.getValue();
1669: for (int j = 0; j < parentArray.size(); j++) {
1670: String parentName = ((Class) parentArray.get(j))
1671: .getName();
1672: if (returnMap.containsKey(parentName)) {
1673: ArrayList childArray = returnMap.get(parentName);
1674: childArray.add(childClass);
1675: } else {
1676: ArrayList childArray = new ArrayList(3);
1677: childArray.add(childClass);
1678: returnMap.put(parentName, childArray);
1679: }
1680: }
1681: }
1682: return returnMap;
1683: }
1684:
1685: /**
1686: * this routine looks for two types of errors (1) a child or parent is NOT in the makers object list (fatal) from XML (2) some
1687: * of the child's parents are listed more than once (warning)
1688: *
1689: * @return true if there are problems in the XML data
1690: */
1691: private boolean findChildParentXMLErrors() {
1692:
1693: boolean problemsInXML = false;
1694: for (Map.Entry<String, ArrayList<Class>> childMap : childParentMap
1695: .entrySet()) {
1696: String childName = childMap.getKey();
1697: ArrayList parentList = childMap.getValue();
1698: ArrayList removeList = new ArrayList(parentList.size());
1699: // check for a problem child
1700: if (!makerObjectsList.containsKey(childName)) {
1701: problemsInXML = true;
1702: LOG
1703: .error(String
1704: .format(
1705: "\nchild %s is not in the fiscal period copy list",
1706: childName));
1707: }
1708: for (int i = 0; i < parentList.size(); i++) {
1709: String parentName = ((Class) parentList.get(i))
1710: .getName();
1711: if (!makerObjectsList.containsKey(parentName)) {
1712: problemsInXML = true;
1713: LOG
1714: .error(String
1715: .format(
1716: "\nparent %s of child %s is not in the fiscal period copy list",
1717: parentName, childName));
1718: }
1719: for (int j = i + 1; j < parentList.size(); j++) {
1720: String anotherParent = ((Class) parentList.get(j))
1721: .getName();
1722: if (parentName.compareTo(anotherParent) == 0) {
1723: // we have a duplicate--add it to the drop list
1724: LOG
1725: .warn(String
1726: .format(
1727: "\nchild %s has parent %s listed twice in XML\n",
1728: childName,
1729: anotherParent));
1730:
1731: removeList.add(j);
1732: }
1733: }
1734: // get rid of the duplicate elements
1735: for (int k = 0; k < removeList.size(); k++) {
1736: parentList.remove(removeList.get(k));
1737: }
1738: }
1739: }
1740: return problemsInXML;
1741: }
1742:
1743: /**
1744: * we can have two kinds of relationships in Kuali (1) a child contains a foreign key to the primary keys of its RI parent (a
1745: * 1:1 relationship in OJB). The parent object is coded in XML with a reference-descriptor (2) a child has a many:1 relationship
1746: * with its RI parent. in this case, the parent has foreign keys into the primary keys of the child. The child object is coded
1747: * in XML with a collection-descriptor this routine gets the parent keys and the child keys for the relationship, so we can
1748: * build a map of all the values for those keys that have already been copied into the parent for the new year. as each child
1749: * row is about to be copied, we check to see whether its key values match one of the sets of values in the parent. if they do
1750: * not, we skip the child row. this routine gives us the key field names we need to accomplish that.
1751: *
1752: * @param childClass
1753: * @param parentClass
1754: * @return foreign keys for parent class
1755: */
1756: private ReturnedPair<String[], String[]> fetchForeignKeysToParent(
1757: Class childClass, Class parentClass) {
1758: ReturnedPair<String[], String[]> returnObject = new ReturnedPair<String[], String[]>();
1759:
1760: /*
1761: * first look for the 1:1 relationship
1762: */
1763: returnObject = fetchFKToParent(childClass, parentClass);
1764: if (!(returnObject.getFirst() == null)) {
1765: return returnObject;
1766: }
1767: /*
1768: * assume it's a 1:m relationship, and look for a collection-descriptor if we can't find one, we'll issue a warning and
1769: * return an empty pair of arrays
1770: */
1771: return (fetchFKToChild(parentClass, childClass));
1772: }
1773:
1774: /**
1775: * This method...
1776: *
1777: * @param parentClass
1778: * @param childClass
1779: * @return foreign keys for child class
1780: */
1781: private ReturnedPair<String[], String[]> fetchFKToChild(
1782: Class parentClass, Class childClass) {
1783: String[] childKeyFields;
1784: String[] parentKeyFields;
1785: ReturnedPair<String[], String[]> returnObject = new ReturnedPair<String[], String[]>();
1786: // first we have to find the attribute name of the parent reference to the child
1787: // class
1788: HashMap<String, Class> collectionObjects = (HashMap<String, Class>) persistenceStructureService
1789: .listCollectionObjectTypes(parentClass);
1790: String attributeName = null;
1791: String childClassID = childClass.getName();
1792: for (Map.Entry<String, Class> attributeMap : collectionObjects
1793: .entrySet()) {
1794: if (childClassID.compareTo(attributeMap.getValue()
1795: .getName()) == 0) {
1796: // the name of the child class matches a collection class
1797: // this is the attribute we want
1798: attributeName = attributeMap.getKey();
1799: break;
1800: }
1801: }
1802: // now we have to use the attribute to look up the inverse foreign keys
1803: if (attributeName == null) {
1804: // write a warning and return an empty key set
1805: LOG
1806: .warn(String
1807: .format(
1808: "\n%s does not have a collection reference to %s\n",
1809: parentClass.getName(), childClassID));
1810: return returnObject;
1811: }
1812: HashMap<String, String> keyMap = (HashMap<String, String>) persistenceStructureService
1813: .getInverseForeignKeysForCollection(parentClass,
1814: attributeName);
1815: childKeyFields = new String[keyMap.size()];
1816: parentKeyFields = new String[keyMap.size()];
1817: // the primary key names refer to child fields
1818: // the foreign key names refer to parent fields
1819: // (persistenceStructureService assumes that the child fields match the
1820: // parent primary key fields in order, AND that the first child field
1821: // corresponds to the first parent primary key field, the second to the
1822: // second, etc. this is apparently OJB's assumption as well. in a 1:many
1823: // relationship, all the parent primary key fields must be used (and
1824: // possibly some other fields)--since the parent row must be unique, but only
1825: // some of the child's primary keys will be used)
1826: int i = 0;
1827: for (Map.Entry<String, String> fkPkPair : keyMap.entrySet()) {
1828: parentKeyFields[i] = fkPkPair.getKey();
1829: childKeyFields[i] = fkPkPair.getValue();
1830: i = i + 1;
1831: }
1832: returnObject.setFirst(childKeyFields);
1833: returnObject.setSecond(parentKeyFields);
1834: return returnObject;
1835: }
1836:
1837: /**
1838: * This method takes a child class and parent class and
1839: *
1840: * @param childClass
1841: * @param parentClass
1842: * @return foreign keys for a given parent class
1843: */
1844: private ReturnedPair<String[], String[]> fetchFKToParent(
1845: Class childClass, Class parentClass) {
1846: String[] childKeyFields;
1847: String[] parentKeyFields;
1848: ReturnedPair<String[], String[]> returnObject = new ReturnedPair<String[], String[]>();
1849: // first we have to find the attribute name of the reference to the parent
1850: // class
1851: HashMap<String, Class> referenceObjects = (HashMap<String, Class>) persistenceStructureService
1852: .listReferenceObjectFields(childClass);
1853: String attributeName = null;
1854: String parentClassID = parentClass.getName();
1855: for (Map.Entry<String, Class> attributeMap : referenceObjects
1856: .entrySet()) {
1857: if (parentClassID.compareTo(attributeMap.getValue()
1858: .getName()) == 0) {
1859: // the name of the parent class matches a reference class
1860: // this is the attribute we want
1861: attributeName = attributeMap.getKey();
1862: break;
1863: }
1864: }
1865: // now we have to use the attribute to look up the foreign keys
1866: if (attributeName == null) {
1867: // write a warning and return an empty key set
1868: LOG.warn(String.format(
1869: "\n%s does not have a child reference to %s\n",
1870: childClass.getName(), parentClassID));
1871: return returnObject;
1872: }
1873: HashMap<String, String> keyMap = (HashMap<String, String>) persistenceStructureService
1874: .getForeignKeysForReference(childClass, attributeName);
1875: childKeyFields = new String[keyMap.size()];
1876: parentKeyFields = new String[keyMap.size()];
1877: // the primary key names refer to parent fields
1878: // the foreign key names refer to child fields
1879: // (persistenceStructureService assumes that the child fields match the
1880: // parent primary key fields in order, AND that the first child field
1881: // corresponds to the first parent primary key field, the second to the
1882: // second, etc. this is apparently OJB's assumption as well.)
1883: int i = 0;
1884: for (Map.Entry<String, String> fkPkPair : keyMap.entrySet()) {
1885: childKeyFields[i] = fkPkPair.getKey();
1886: parentKeyFields[i] = fkPkPair.getValue();
1887: i = i + 1;
1888: }
1889: returnObject.setFirst(childKeyFields);
1890: returnObject.setSecond(parentKeyFields);
1891: return returnObject;
1892: }
1893:
1894: /**
1895: * This method determines the capacity of the hash based on the item count returned by the query
1896: *
1897: * @param queryID
1898: * @return hash capacity based on query result set size
1899: */
1900: private Integer hashCapacity(ReportQueryByCriteria queryID) {
1901: // this corresponds to a load factor of a little more than the default load factor
1902: // of .75
1903: // (a rehash supposedly occurs when the actual number of elements exceeds
1904: // hashcapacity*(load factor). we want to avoid a rehash)
1905: // N rows < .75*capacity ==> capacity > 4N/3 or 1.3333N We add a little slop.
1906: Integer actualCount = new Integer(
1907: getPersistenceBrokerTemplate().getCount(queryID));
1908: return ((Integer) ((Double) (actualCount.floatValue() * (1.45)))
1909: .intValue());
1910: }
1911:
1912: private PersistenceStructureWindow persistenceStructureWindow = null;
1913:
1914: /**
1915: * turnOffCascades should always be called, but if it hasn't been, there is no need to call this
1916: *
1917: * @see org.kuali.module.chart.dao.FiscalYearMakersDao#resetCascades()
1918: */
1919: public void resetCascades() {
1920: if (persistenceStructureWindow == null) {
1921: return;
1922: }
1923: ;
1924: persistenceStructureWindow.restoreCascading();
1925: }
1926:
1927: /**
1928: * this routine is designed to solve a problem caused by auto-xxx settings in the OJB-repostiory auto-update or auto-delete
1929: * settings other than "none" will cause row(s) for a linked object to be written or deleted as soon as the row for the linking
1930: * object is. this circumvents our parent-child paradigm by which we ensure deletes and copies are done in an order that will
1931: * not violate referential integrity constraints. for example, suppose a parent A is linked to a child B, which has
1932: * auto-update="object". B may have an RI constraint on C, while A has nothing to do with C. our copy order will allow A to be
1933: * copied before C. auto-update="object" copies row(s) from B at the same time a row from A is copied. since no rows from C have
1934: * been copied yet (C follows A in the copy order, the attempt to store the rows of B will violate RI--the required rows from C
1935: * are not in the DB yet. (an example as of October, 2007 is A = OrganizationReversion, B = OrganizationReversionDetail, and C =
1936: * ObjectCode) this routine dynamically switches off the auto-update and auto-delete in the OJB repository loaded in memory.
1937: * this should affect only the current run, makes no permanent changes, and will not affect the performance of any documents.
1938: * the assumption is that this code is running in its own Java container, which will go away when the run is complete.
1939: */
1940: private void turnOffCascades() {
1941:
1942: // set up the window into the OJB persistence structure
1943: persistenceStructureWindow = new PersistenceStructureWindow();
1944: for (Map.Entry<String, ArrayList<Class>> childMap : childParentMap
1945: .entrySet()) {
1946: // get the class from the child name
1947: Class childClass = makerObjectsList.get(childMap.getKey());
1948: ArrayList<Class> parentList = childMap.getValue();
1949: for (int i = 0; i < parentList.size(); i++) {
1950: Class parentClass = parentList.get(i);
1951: persistenceStructureWindow.inhibitCascading(childClass,
1952: parentClass);
1953: persistenceStructureWindow.inhibitCascading(
1954: parentClass, childClass);
1955: }
1956: }
1957:
1958: }
1959:
1960: /*******************************************************************************************************************************
1961: * these classes belong in the persistence structure service. pending that, we use them here because we need them. we indicate
1962: * below which should be public and which should not. this should only be used in BATCH, where nothing that needs to auto-update
1963: * or auto-delete is likely to access the parent-child objects. for fiscal year makers, this condition is met. for batch
1964: * routines that use a plug-in or create and store documents, it may not be.
1965: ******************************************************************************************************************************/
1966: /**
1967: * This class
1968: */
1969: private class PersistenceStructureWindow {
1970: private DescriptorRepository descriptorRepository;
1971:
1972: // these save enough information from the repository so we can restore the auto-xxx fields we change
1973: // the size of the hashmaps should be more than sufficient to change 16-17 tables
1974: // we shouldn't have to change anywhere close to that many
1975: private HashMap<CollectionDescriptor, String[]> collectionStore = new HashMap<CollectionDescriptor, String[]>(
1976: 25);
1977: private HashMap<CollectionDescriptor, String[]> collectionDelete = new HashMap<CollectionDescriptor, String[]>(
1978: 25);
1979: private HashMap<ObjectReferenceDescriptor, String[]> objectReferenceStore = new HashMap<ObjectReferenceDescriptor, String[]>(
1980: 25);
1981: private HashMap<ObjectReferenceDescriptor, String[]> objectReferenceDelete = new HashMap<ObjectReferenceDescriptor, String[]>(
1982: 25);
1983:
1984: public PersistenceStructureWindow() {
1985: MetadataManager metadataManager = MetadataManager
1986: .getInstance();
1987: descriptorRepository = metadataManager
1988: .getGlobalRepository();
1989:
1990: }
1991:
1992: /**
1993: * This method looks for reference descriptors and collection descriptors in the source class that refer to the target class
1994: * and specify an auto-delete or auto-update. it turns those functions off for the remainder of the batch run.
1995: *
1996: * @param referencingClass
1997: * @param targetClass
1998: */
1999: public void inhibitCascading(Class referencingClass,
2000: Class targetClass) {
2001: /* this method should be public in the persistence structure service */
2002:
2003: /* a given class will not have a 1:1 reference and a 1:m reference to the same target class */
2004: if (fixReferences(referencingClass, targetClass)) {
2005: return;
2006: }
2007: fixCollections(referencingClass, targetClass);
2008: }
2009:
2010: /**
2011: * This method returns a specific ClassDescriptor based on a BusinessObject class
2012: *
2013: * @param boClass
2014: * @return class descriptor for this BO class
2015: */
2016: private ClassDescriptor getOJBDescriptor(Class boClass) {
2017: ClassDescriptor classDescriptor = null;
2018: try {
2019: classDescriptor = descriptorRepository
2020: .getDescriptorFor(boClass);
2021: } catch (ClassNotPersistenceCapableException ex) {
2022: LOG.warn(String.format(
2023: "\n\nClass %s is non-presistable\n--> %s",
2024: boClass.getName(), ex.getMessage()));
2025: // we'll just let things go at this point and hope for the best with RI
2026: return classDescriptor;
2027: }
2028: return classDescriptor;
2029: }
2030:
2031: /**
2032: * This method turns off cascading updates on the target class as it is referenced from the referencing class
2033: *
2034: * @param referencingClass
2035: * @param targetClass
2036: * @return true if it finds the reference (or isn't persistable), false if it can't find the reference
2037: */
2038: private boolean fixReferences(Class referencingClass,
2039: Class targetClass) {
2040: ClassDescriptor classDescriptor = getOJBDescriptor(referencingClass);
2041: if (classDescriptor == null) {
2042: // class isn't persistable--no reason to continue
2043: return true;
2044: }
2045: Collection<ObjectReferenceDescriptor> referenceIDAttributes = classDescriptor
2046: .getObjectReferenceDescriptors(false);
2047: for (ObjectReferenceDescriptor objReferenceDescriptor : referenceIDAttributes) {
2048: if (targetClass.getName().compareTo(
2049: objReferenceDescriptor.getItemClassName()) == 0) {
2050: LOG.debug(String.format(
2051: "\n\nfound reference to %s in %s",
2052: targetClass.getName(), referencingClass
2053: .getName()));
2054:
2055: if (objReferenceDescriptor.getCascadingDelete() != ObjectReferenceDescriptor.CASCADE_NONE) {
2056: // we want to issue a message whenever we toggle--so store three things
2057: String[] infoString = {
2058: targetClass.getName(),
2059: referencingClass.getName(),
2060: objReferenceDescriptor
2061: .getCascadeAsString(objReferenceDescriptor
2062: .getCascadingDelete()) };
2063: objectReferenceDelete.put(
2064: objReferenceDescriptor, infoString);
2065: objReferenceDescriptor
2066: .setCascadingDelete(ObjectReferenceDescriptor.CASCADE_NONE);
2067: LOG
2068: .warn(String
2069: .format(
2070: "\nreset auto-delete = %s\nfor %s in %s",
2071: infoString[2],
2072: targetClass.getName(),
2073: referencingClass
2074: .getName()));
2075: }
2076: if (objReferenceDescriptor.getCascadingStore() != ObjectReferenceDescriptor.CASCADE_NONE) {
2077: // we want to issue a message whenever we toggle--so store three things
2078: String[] infoString = {
2079: targetClass.getName(),
2080: referencingClass.getName(),
2081: objReferenceDescriptor
2082: .getCascadeAsString(objReferenceDescriptor
2083: .getCascadingStore()) };
2084: objectReferenceStore.put(
2085: objReferenceDescriptor, infoString);
2086: objReferenceDescriptor
2087: .setCascadingStore(ObjectReferenceDescriptor.CASCADE_NONE);
2088: LOG
2089: .warn(String
2090: .format(
2091: "\nreset auto-update = %s\nfor %s in %s",
2092: infoString[2],
2093: targetClass.getName(),
2094: referencingClass
2095: .getName()));
2096: }
2097: return true;
2098: // a given class will not have a reference-id and a collection-ref-id to the same target class
2099: }
2100: }
2101: return false;
2102: }
2103:
2104: /**
2105: * This method turns off cascading updates on the target class as it is referenced from the referencing class as a
2106: * collection
2107: *
2108: * @param referencingClass
2109: * @param targetClass
2110: * @return true if it finds the reference (or isn't persistable), false if it can't find the reference
2111: */
2112: private boolean fixCollections(Class referencingClass,
2113: Class targetClass) {
2114: ClassDescriptor classDescriptor = getOJBDescriptor(referencingClass);
2115: if (classDescriptor == null) {
2116: // class isn't persistable--no reason to continue
2117: return true;
2118: }
2119: Collection<CollectionDescriptor> collectionIDAttributes = classDescriptor
2120: .getCollectionDescriptors(false);
2121: for (CollectionDescriptor collectionDescriptor : collectionIDAttributes) {
2122: if (targetClass.getName().compareTo(
2123: collectionDescriptor.getItemClassName()) == 0) {
2124: LOG
2125: .debug(String
2126: .format(
2127: "\n\nfound collection reference to %s in %s",
2128: targetClass.getName(),
2129: referencingClass.getName()));
2130:
2131: if (collectionDescriptor.getCascadingDelete() != CollectionDescriptor.CASCADE_NONE) {
2132: // we want to issue a message whenever we toggle--so store three things
2133: String[] infoString = {
2134: targetClass.getName(),
2135: referencingClass.getName(),
2136: collectionDescriptor
2137: .getCascadeAsString(collectionDescriptor
2138: .getCascadingDelete()) };
2139: collectionDelete.put(collectionDescriptor,
2140: infoString);
2141: collectionDescriptor
2142: .setCascadingDelete(CollectionDescriptor.CASCADE_NONE);
2143: LOG
2144: .warn(String
2145: .format(
2146: "\nreset auto-delete = %s \nfor %s in %s",
2147: infoString[2],
2148: targetClass.getName(),
2149: referencingClass
2150: .getName()));
2151: }
2152: if (collectionDescriptor.getCascadingStore() != CollectionDescriptor.CASCADE_NONE) {
2153: // we want to issue a message whenever we toggle--so store three things
2154: String[] infoString = {
2155: targetClass.getName(),
2156: referencingClass.getName(),
2157: collectionDescriptor
2158: .getCascadeAsString(collectionDescriptor
2159: .getCascadingStore()) };
2160: collectionStore.put(collectionDescriptor,
2161: infoString);
2162: collectionDescriptor
2163: .setCascadingStore(CollectionDescriptor.CASCADE_NONE);
2164: LOG
2165: .warn(String
2166: .format(
2167: "\nreset auto-update = %s \nfor %s in %s",
2168: infoString[2],
2169: targetClass.getName(),
2170: referencingClass
2171: .getName()));
2172: }
2173: return true;
2174: // a given class will not have a reference-id and a collection-ref-id to the same target class
2175: }
2176: }
2177: return false;
2178: }
2179:
2180: /**
2181: * This method restores the cascading saves and updates to what they were before the change
2182: */
2183: public void restoreCascading() {
2184: // auto deletes in collections
2185: for (Map.Entry<CollectionDescriptor, String[]> restoreTargets : collectionDelete
2186: .entrySet()) {
2187: String[] infoString = restoreTargets.getValue();
2188: CollectionDescriptor collectionDesc = restoreTargets
2189: .getKey();
2190: collectionDesc.setCascadingDelete(infoString[2]);
2191: LOG.warn(String.format(
2192: "\nauto-delete reset to %s\nfor %s in %s",
2193: collectionDesc
2194: .getCascadeAsString(collectionDesc
2195: .getCascadingDelete()),
2196: infoString[0], infoString[1]));
2197: }
2198: // auto updates in collections
2199: for (Map.Entry<CollectionDescriptor, String[]> restoreTargets : collectionStore
2200: .entrySet()) {
2201: String[] infoString = restoreTargets.getValue();
2202: CollectionDescriptor collectionDesc = restoreTargets
2203: .getKey();
2204: collectionDesc.setCascadingStore(infoString[2]);
2205: LOG.warn(String.format(
2206: "\nauto-update reset to %s\nfor %s in %s",
2207: collectionDesc
2208: .getCascadeAsString(collectionDesc
2209: .getCascadingStore()),
2210: infoString[0], infoString[1]));
2211: }
2212: // auto deletes in references
2213: for (Map.Entry<ObjectReferenceDescriptor, String[]> restoreTargets : objectReferenceDelete
2214: .entrySet()) {
2215: String[] infoString = restoreTargets.getValue();
2216: ObjectReferenceDescriptor objReferenceDesc = restoreTargets
2217: .getKey();
2218: objReferenceDesc.setCascadingDelete(infoString[2]);
2219: LOG.warn(String.format(
2220: "\nauto-delete reset to %s\nfor %s in %s",
2221: objReferenceDesc
2222: .getCascadeAsString(objReferenceDesc
2223: .getCascadingDelete()),
2224: infoString[0], infoString[1]));
2225: }
2226: // auto updates in collections
2227: for (Map.Entry<ObjectReferenceDescriptor, String[]> restoreTargets : objectReferenceStore
2228: .entrySet()) {
2229: String[] infoString = restoreTargets.getValue();
2230: ObjectReferenceDescriptor objReferenceDesc = restoreTargets
2231: .getKey();
2232: objReferenceDesc.setCascadingStore(infoString[2]);
2233: LOG.warn(String.format(
2234: "\nauto-update reset to %s\nfor %s in %s",
2235: objReferenceDesc
2236: .getCascadeAsString(objReferenceDesc
2237: .getCascadingStore()),
2238: infoString[0], infoString[1]));
2239: }
2240: }
2241: }
2242:
2243: /**
2244: * this is a handy junk inner class that allows us to return two things from a method
2245: */
2246: private class ReturnedPair<S, T> {
2247: S firstObject;
2248: T secondObject;
2249:
2250: public ReturnedPair() {
2251: this .firstObject = null;
2252: this .secondObject = null;
2253: }
2254:
2255: public S getFirst() {
2256: return this .firstObject;
2257: }
2258:
2259: public T getSecond() {
2260: return this .secondObject;
2261: }
2262:
2263: public void setFirst(S firstObject) {
2264: this .firstObject = firstObject;
2265: }
2266:
2267: public void setSecond(T secondObject) {
2268: this .secondObject = secondObject;
2269: }
2270: }
2271:
2272: // @@TODO: remove this test routine
2273: public void testUpdateTwoDigitYear() {
2274: String oldYear = new String("08");
2275: String newYear = new String("11");
2276: String testString = new String(
2277: "08-09 x 08 07-08 08-09 09 08 08");
2278: String newString = updateTwoDigitYear(newYear, oldYear,
2279: testString);
2280: LOG
2281: .warn(String
2282: .format(
2283: "\n test of updateTwoDigitYear:\n input = %s\n output = %s\n from: %s, to:%s ",
2284: testString, newString, oldYear, newYear));
2285: testString = new String("x08-09 x 08 07-08 08-09 09 tail");
2286: newString = updateTwoDigitYear(newYear, oldYear, testString);
2287: LOG
2288: .warn(String
2289: .format(
2290: "\n test of updateTwoDigitYear:\n input = %s\n output = %s\n from: %s, to:%s ",
2291: testString, newString, oldYear, newYear));
2292: testString = new String(" nada ");
2293: newString = updateTwoDigitYear(newYear, oldYear, testString);
2294: LOG
2295: .warn(String
2296: .format(
2297: "\n test of updateTwoDigitYear:\n input = %s\n output = %s\n from: %s, to:%s ",
2298: testString, newString, oldYear, newYear));
2299: }
2300:
2301: // TODO: remove these test routines
2302:
2303: public void testRIRelationships() throws NoSuchMethodException,
2304: IllegalAccessException, InvocationTargetException {
2305: // print the object list
2306: LOG.warn(String.format("\n\nFiscalYearMakersObjects:\n\n"));
2307: for (Map.Entry<String, Class> makerObjects : makerObjectsList
2308: .entrySet()) {
2309: LOG.warn(String.format("\nkey: %s, class name: %s",
2310: makerObjects.getKey(), makerObjects.getValue()
2311: .getName()));
2312: }
2313: // print the child/parent list
2314: LOG.warn(String.format("\n\nchild key, parent classes:\n\n"));
2315: for (Map.Entry<String, ArrayList<Class>> childParents : childParentMap
2316: .entrySet()) {
2317: Iterator<Class> parents = childParents.getValue()
2318: .iterator();
2319: LOG.warn(String.format("\nchild: %s has %d parents",
2320: childParents.getKey(), childParents.getValue()
2321: .size()));
2322: Class childClass = makerObjectsList.get(childParents
2323: .getKey());
2324: while (parents.hasNext()) {
2325: Class parentClass = parents.next();
2326: LOG.warn(String.format(
2327: "\n parent class name: %s", parentClass
2328: .getName()));
2329: ReturnedPair<String[], String[]> keySets = fetchForeignKeysToParent(
2330: childClass, parentClass);
2331: String[] childKeys = keySets.getFirst();
2332: String[] parentKeys = keySets.getSecond();
2333: LOG.warn(String
2334: .format("\n ------child keys-----"));
2335: for (int i = 0; i < childKeys.length; i++) {
2336: LOG
2337: .warn(String.format("\n %s",
2338: childKeys[i]));
2339: }
2340: LOG.warn(String
2341: .format("\n ------parent keys-----"));
2342: for (int i = 0; i < parentKeys.length; i++) {
2343: LOG.warn(String
2344: .format("\n %s", parentKeys[i]));
2345: }
2346: }
2347: }
2348: // print the delete order
2349: LOG.warn(String.format("\n\nDelete Order:\n\n"));
2350: LinkedHashMap<String, Class> deleteOrder = getDeleteOrder();
2351: for (Map.Entry<String, Class> orderedDeleteList : deleteOrder
2352: .entrySet()) {
2353: LOG.warn(String.format("\n key: %s, class name: %s",
2354: orderedDeleteList.getKey(), orderedDeleteList
2355: .getValue().getName()));
2356: }
2357: // print the copy order
2358: // (this should fail with an exception if not all the objects have been
2359: // added to setUpRun)
2360: LOG.warn(String.format("\n\nCopy Order:\n\n"));
2361: LinkedHashMap<String, FiscalYearMakersCopyAction> copyOrder = setUpRun(
2362: 2010, false);
2363: for (Map.Entry<String, FiscalYearMakersCopyAction> copyOrderList : copyOrder
2364: .entrySet()) {
2365: if (copyOrderList.getValue() == null) {
2366: LOG.warn(String.format(
2367: "\n key: %s, object value is null",
2368: copyOrderList.getKey()));
2369: } else {
2370: LOG.warn(String.format("\n key: %s, object value %s",
2371: copyOrderList.getKey(), copyOrderList
2372: .getValue().toString()));
2373: }
2374: }
2375: // reprint this, to make sure it hasn't been corrupted in building
2376: // the copy order data structure
2377: LOG.warn(String.format("\n\nchild key, parent classes:\n\n"));
2378: for (Map.Entry<String, ArrayList<Class>> childParents : childParentMap
2379: .entrySet()) {
2380: Iterator<Class> parents = childParents.getValue()
2381: .iterator();
2382: LOG.warn(String.format("\nchild: %s has %d parents",
2383: childParents.getKey(), childParents.getValue()
2384: .size()));
2385: while (parents.hasNext()) {
2386: LOG.warn(String.format(
2387: "\n parent class name: %s", parents
2388: .next().getName()));
2389: }
2390: }
2391: // now print the objects which are a year behind
2392: LOG
2393: .warn(String
2394: .format("\n\nLagging Copy Cycle (one year behind):\n\n"));
2395: Iterator<String> laggardList = laggingCopyCycle.iterator();
2396: while (laggardList.hasNext()) {
2397: LOG.warn(String.format("\n %s", (String) laggardList
2398: .next()));
2399: }
2400: LOG.warn(String.format("\n\n"));
2401: // we want to test the code that enforces RI copy integrity by checking
2402: // that child keys to be copied exist in the (already copied) parent keys
2403: // we use 2011. FS_OPTIONS_T exists, but CA_OBJECT_CODE_T does not
2404: // so, CA_SUB_OBJECT_CD_T should fail, but CA_OBJECT_CODE_T should succeed.
2405: // we add a third which should work, to test the need to specify the generic
2406: // class on the constructor
2407: Integer baseYear = new Integer(2009);
2408: Integer requestYear = baseYear + 1;
2409: ParentKeyChecker<ObjectCode> objCodeChk = new ParentKeyChecker<ObjectCode>(
2410: ObjectCode.class, requestYear);
2411: Criteria yearCriteria = new Criteria();
2412: yearCriteria.addEqualTo(
2413: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, baseYear);
2414: yearCriteria.addLessThan("ROWNUM", new Integer(3));
2415: QueryByCriteria queryID = new QueryByCriteria(ObjectCode.class,
2416: yearCriteria);
2417: Iterator<ObjectCode> objsReturned = getPersistenceBrokerTemplate()
2418: .getIteratorByQuery(queryID);
2419: while (objsReturned.hasNext()) {
2420: ObjectCode rowReturned = objsReturned.next();
2421: PropertyUtils.setSimpleProperty(rowReturned,
2422: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
2423: requestYear);
2424: LOG.warn(String.format("\nObjectCode key in parents? %b",
2425: objCodeChk.childRowSatisfiesRI(rowReturned)));
2426: }
2427: // now we're going to do the sub object code
2428: ParentKeyChecker<SubObjCd> subObjCdChk = new ParentKeyChecker<SubObjCd>(
2429: SubObjCd.class, requestYear);
2430: queryID = new QueryByCriteria(SubObjCd.class, yearCriteria);
2431: Iterator<SubObjCd> subObjsReturned = getPersistenceBrokerTemplate()
2432: .getIteratorByQuery(queryID);
2433: while (subObjsReturned.hasNext()) {
2434: SubObjCd rowReturned = subObjsReturned.next();
2435: PropertyUtils.setSimpleProperty(rowReturned,
2436: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
2437: requestYear);
2438: LOG.warn(String.format(
2439: "\nsub object code key in parents? %b", subObjCdChk
2440: .childRowSatisfiesRI(rowReturned)));
2441: }
2442: // we need to test OrganizationReversionDetail
2443: ParentKeyChecker<OrganizationReversionDetail> OrgRevDtlChk = new ParentKeyChecker<OrganizationReversionDetail>(
2444: OrganizationReversionDetail.class, requestYear - 1);
2445: Criteria laggingYearCriteria = new Criteria();
2446: laggingYearCriteria.addEqualTo(
2447: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
2448: baseYear - 1);
2449: laggingYearCriteria.addLessThan("ROWNUM", new Integer(3));
2450: queryID = new QueryByCriteria(
2451: OrganizationReversionDetail.class, laggingYearCriteria);
2452: Iterator<OrganizationReversionDetail> OrgRevDtlReturned = getPersistenceBrokerTemplate()
2453: .getIteratorByQuery(queryID);
2454: while (OrgRevDtlReturned.hasNext()) {
2455: OrganizationReversionDetail rowReturned = OrgRevDtlReturned
2456: .next();
2457: PropertyUtils.setSimpleProperty(rowReturned,
2458: KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR,
2459: requestYear - 1);
2460: LOG.warn(String.format(
2461: "\nOrganizationReversionDetail key in parents? %b",
2462: OrgRevDtlChk.childRowSatisfiesRI(rowReturned)));
2463: }
2464: LOG.warn(String.format("\n\nend RI test\n\n"));
2465: }
2466:
2467: }
|