001/*
002 * Copyright 2008 The Kuali Foundation
003 * 
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 * http://www.opensource.org/licenses/ecl2.php
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.module.purap.batch.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.ole.module.purap.PurapParameterConstants;
020import org.kuali.ole.module.purap.batch.service.PurapRunDateService;
021import org.kuali.ole.sys.service.impl.OleParameterConstants.PURCHASING_BATCH;
022import org.kuali.rice.coreservice.framework.parameter.ParameterService;
023
024import java.util.Calendar;
025import java.util.Date;
026import java.util.StringTokenizer;
027
028public 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        } else {
047            adjustTimeOfDay(currentCal, false);
048        }
049
050        return currentCal.getTime();
051    }
052
053    /**
054     * Adjusts the time of day if necessary, possibly depending on whether the execution time was before or after cutoff time
055     *
056     * @param calendar
057     * @param appliedCutoff true if the execution time was before the cutoff
058     */
059    protected void adjustTimeOfDay(Calendar calendar, boolean appliedCutoff) {
060        calendar.set(Calendar.HOUR_OF_DAY, 0);
061        calendar.set(Calendar.MINUTE, 0);
062        calendar.set(Calendar.SECOND, 0);
063        calendar.set(Calendar.MILLISECOND, 0);
064    }
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         *
116         * @param hour   the cutoff hour
117         * @param minute the cutoff minute
118         * @param second the cutoff second
119         */
120        protected CutoffTime(int hour, int minute, int second) {
121            this.hour = hour;
122            this.minute = minute;
123            this.second = second;
124        }
125    }
126
127    /**
128     * Parses a String representation of the cutoff time
129     *
130     * @param cutoffTime the cutoff time String to parse
131     * @return a record holding the cutoff time
132     */
133    protected CutoffTime parseCutoffTime(String cutoffTime) {
134        if (StringUtils.isBlank(cutoffTime)) {
135            return null;
136        } else {
137            cutoffTime = cutoffTime.trim();
138            if (LOG.isDebugEnabled()) {
139                LOG.debug("Cutoff time value found: " + cutoffTime);
140            }
141            StringTokenizer st = new StringTokenizer(cutoffTime, ":", false);
142
143            try {
144                String hourStr = st.nextToken();
145                String minuteStr = st.nextToken();
146                String secondStr = st.nextToken();
147
148                int hourInt = Integer.parseInt(hourStr, 10);
149                int minuteInt = Integer.parseInt(minuteStr, 10);
150                int secondInt = Integer.parseInt(secondStr, 10);
151
152                if (hourInt < 0 || hourInt > 23 || minuteInt < 0 || minuteInt > 59 || secondInt < 0 || secondInt > 59) {
153                    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");
154                }
155                return new CutoffTime(hourInt, minuteInt, secondInt);
156            } catch (Exception e) {
157                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.");
158            }
159        }
160    }
161
162    /**
163     * Retrieves the cutoff time from a repository.
164     *
165     * @return a time of day in the format "HH:mm:ss", where HH, mm, ss are defined in the java.text.SimpleDateFormat class. In
166     *         particular, 0 <= hour <= 23, 0 <= minute <= 59, and 0 <= second <= 59
167     */
168    protected String retrieveCutoffTimeValue() {
169        String value = parameterService.getParameterValueAsString(PURCHASING_BATCH.class, PurapParameterConstants.PRE_DISBURSEMENT_EXTRACT_CUTOFF_TIME);
170        if (StringUtils.isBlank(value)) {
171            LOG.info("Unable to retrieve parameter for PURAP process cutoff date.  Defaulting to no cutoff time (i.e. midnight)");
172            value = null;
173        }
174        return value;
175    }
176
177    public void setParameterService(ParameterService parameterService) {
178        this.parameterService = parameterService;
179    }
180}