001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.purap.batch.service.impl;
017    
018    import java.util.Calendar;
019    import java.util.Date;
020    import java.util.StringTokenizer;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.kuali.kfs.module.purap.PurapParameterConstants;
024    import org.kuali.kfs.module.purap.batch.service.PurapRunDateService;
025    import org.kuali.kfs.sys.service.impl.KfsParameterConstants.PURCHASING_BATCH;
026    import org.kuali.rice.kns.service.ParameterService;
027    
028    public class PurapRunDateServiceImpl implements PurapRunDateService {
029        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapRunDateServiceImpl.class);
030    
031        private ParameterService parameterService;
032        
033        /**
034         * Determines the date to assume when running the batch processes
035         */
036        public Date calculateRunDate(Date executionDate) {
037            Calendar currentCal = Calendar.getInstance();
038            currentCal.setTime(executionDate);
039    
040            CutoffTime cutoffTime = parseCutoffTime(retrieveCutoffTimeValue());
041    
042            if (isCurrentDateAfterCutoff(currentCal, cutoffTime)) {
043                // go back one day
044                currentCal.add(Calendar.DAY_OF_MONTH, 1);
045                adjustTimeOfDay(currentCal, true);
046            }
047            else {
048                adjustTimeOfDay(currentCal, false);
049            }
050            
051            return currentCal.getTime();
052        }
053    
054        /**
055         * Adjusts the time of day if necessary, possibly depending on whether the execution time was before or after cutoff time
056         * 
057         * @param calendar
058         * @param appliedCutoff true if the execution time was before the cutoff
059         */
060        protected void adjustTimeOfDay(Calendar calendar, boolean appliedCutoff) {
061            calendar.set(Calendar.HOUR_OF_DAY, 0);
062            calendar.set(Calendar.MINUTE, 0);
063            calendar.set(Calendar.SECOND, 0);
064            calendar.set(Calendar.MILLISECOND, 0);
065        }
066        /**
067         * Determines if the given calendar time is before the given cutoff time
068         * 
069         * @param currentCal the current time
070         * @param cutoffTime the "start of the day" cut off time
071         * @return true if the current time is before the cutoff, false otherwise
072         */
073        protected boolean isCurrentDateAfterCutoff(Calendar currentCal, CutoffTime cutoffTime) {
074            if (cutoffTime != null) {
075                // if cutoff date is not properly defined
076                // 24 hour clock (i.e. hour is 0 - 23)
077    
078                // clone the calendar so we get the same month, day, year
079                // then change the hour, minute, second fields
080                // then see if the cutoff is before or after
081                Calendar cutoffCal = (Calendar) currentCal.clone();
082                cutoffCal.setLenient(false);
083                cutoffCal.set(Calendar.HOUR_OF_DAY, cutoffTime.hour);
084                cutoffCal.set(Calendar.MINUTE, cutoffTime.minute);
085                cutoffCal.set(Calendar.SECOND, cutoffTime.second);
086                cutoffCal.set(Calendar.MILLISECOND, 0);
087    
088                return currentCal.after(cutoffCal);
089            }
090            // if cutoff date is not properly defined, then it is considered to be before the cutoff, that is, no cutoff date will be applied
091            return false;
092        }
093    
094        /**
095         * Holds the hour, minute, and second of a given cut off time
096         */
097        protected class CutoffTime {
098            /**
099             * 24 hour time, from 0-23, inclusive
100             */
101            protected int hour;
102    
103            /**
104             * From 0-59, inclusive
105             */
106            protected int minute;
107    
108            /**
109             * From 0-59, inclusive
110             */
111            protected int second;
112    
113            /**
114             * Constructs a RunDateServiceImpl instance
115             * @param hour the cutoff hour
116             * @param minute the cutoff minute
117             * @param second the cutoff second
118             */
119            protected CutoffTime(int hour, int minute, int second) {
120                this.hour = hour;
121                this.minute = minute;
122                this.second = second;
123            }
124        }
125    
126        /**
127         * Parses a String representation of the cutoff time
128         * 
129         * @param cutoffTime the cutoff time String to parse
130         * @return a record holding the cutoff time
131         */
132        protected CutoffTime parseCutoffTime(String cutoffTime) {
133            if (StringUtils.isBlank(cutoffTime)) {
134                return null;
135            }
136            else {
137                cutoffTime = cutoffTime.trim();
138                LOG.debug("Cutoff time value found: " + cutoffTime);
139                StringTokenizer st = new StringTokenizer(cutoffTime, ":", false);
140    
141                try {
142                    String hourStr = st.nextToken();
143                    String minuteStr = st.nextToken();
144                    String secondStr = st.nextToken();
145    
146                    int hourInt = Integer.parseInt(hourStr, 10);
147                    int minuteInt = Integer.parseInt(minuteStr, 10);
148                    int secondInt = Integer.parseInt(secondStr, 10);
149    
150                    if (hourInt < 0 || hourInt > 23 || minuteInt < 0 || minuteInt > 59 || secondInt < 0 || secondInt > 59) {
151                        throw new IllegalArgumentException("Cutoff time must be in the format \"HH:mm:ss\", where HH, mm, ss are defined in the java.text.SimpleDateFormat class.  In particular, 0 <= hour <= 23, 0 <= minute <= 59, and 0 <= second <= 59");
152                    }
153                    return new CutoffTime(hourInt, minuteInt, secondInt);
154                }
155                catch (Exception e) {
156                    throw new IllegalArgumentException("Cutoff time should either be null, or in the format \"HH:mm:ss\", where HH, mm, ss are defined in the java.text.SimpleDateFormat class.");
157                }
158            }
159        }
160    
161        /**
162         * Retrieves the cutoff time from a repository.
163         * 
164         * @return a time of day in the format "HH:mm:ss", where HH, mm, ss are defined in the java.text.SimpleDateFormat class. In
165         *         particular, 0 <= hour <= 23, 0 <= minute <= 59, and 0 <= second <= 59
166         */
167        protected String retrieveCutoffTimeValue() {
168            String value = parameterService.getParameterValue(PURCHASING_BATCH.class, PurapParameterConstants.PRE_DISBURSEMENT_EXTRACT_CUTOFF_TIME);
169            if (StringUtils.isBlank(value)) {
170                LOG.info("Unable to retrieve parameter for PURAP process cutoff date.  Defaulting to no cutoff time (i.e. midnight)");
171                value = null;
172            }
173            return value;
174        }
175    
176        public void setParameterService(ParameterService parameterService) {
177            this.parameterService = parameterService;
178        }
179    }