/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.platform.scheduler2.blockout; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.pentaho.platform.api.scheduler2.ComplexJobTrigger; import org.pentaho.platform.api.scheduler2.CronJobTrigger; import org.pentaho.platform.api.scheduler2.IBlockoutManager; import org.pentaho.platform.api.scheduler2.IJobTrigger; import org.pentaho.platform.api.scheduler2.IScheduler; import org.pentaho.platform.api.scheduler2.Job; import org.pentaho.platform.api.scheduler2.SchedulerException; import org.pentaho.platform.api.scheduler2.SimpleJobTrigger; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.scheduler2.quartz.QuartzJobKey; import org.pentaho.platform.scheduler2.quartz.QuartzScheduler; import org.quartz.Trigger; public class BlockoutManagerUtil { /** * Standard Units of Time */ public static enum TIME { MILLISECOND( 1 ), SECOND( MILLISECOND.time * 1000 ), MINUTE( SECOND.time * 60 ), HOUR( MINUTE.time * 60 ), DAY( HOUR.time * 24 ), WEEK( DAY.time * 7 ), YEAR( DAY.time * 365 ); public long time; private TIME( long time ) { this.time = time; } } public static boolean willFire( IJobTrigger jobTrigger, List<IJobTrigger> blockOutTriggers, IScheduler scheduler ) { // Short return as to avoid having to calculate fire times if ( blockOutTriggers.isEmpty() ) { return true; } List<Date> fireTimes = getFireTimes( jobTrigger, scheduler ); for ( IJobTrigger blockOutJobTrigger : blockOutTriggers ) { // We must verify further if the schedule is blocked completely or if it will fire if ( willBlockSchedule( jobTrigger, blockOutJobTrigger, scheduler ) ) { boolean isBlockoutComplex = isComplexTrigger( blockOutJobTrigger ); // If recurrence intervals are the same, it will never fire if ( !isBlockoutComplex && !isComplexTrigger( jobTrigger ) && getRecurrenceInterval( blockOutJobTrigger ) == getRecurrenceInterval( jobTrigger ) ) { return false; } List<Date> blockoutFireTimes = null; if ( isBlockoutComplex ) { blockoutFireTimes = getFireTimes( blockOutJobTrigger, scheduler ); } // Loop through fire times and verify whether block out is blocking the schedule completely boolean scheduleCompletelyBlocked = true; for ( Date fireTime : fireTimes ) { scheduleCompletelyBlocked = isBlockoutComplex ? willComplexBlockOutTriggerBlockDate( blockOutJobTrigger, blockoutFireTimes, fireTime ) : willBlockDate( blockOutJobTrigger, fireTime, scheduler ); if ( !scheduleCompletelyBlocked ) { break; } } // Return false if after n iterations if ( scheduleCompletelyBlocked ) { return false; } } } return true; } public static boolean willBlockSchedule( IJobTrigger scheduleTrigger, IJobTrigger blockOutJobTrigger, IScheduler scheduler ) { boolean isScheduleTriggerComplex = isComplexTrigger( scheduleTrigger ); boolean isBlockOutTriggerComplex = isComplexTrigger( blockOutJobTrigger ); // Both Schedule and BlockOut are complex if ( isScheduleTriggerComplex && isBlockOutTriggerComplex ) { return willComplexBlockOutBlockComplexScheduleTrigger( blockOutJobTrigger, scheduleTrigger, scheduler ); } // Complex Schedule Trigger if ( isScheduleTriggerComplex ) { return willBlockComplexScheduleTrigger( scheduleTrigger, blockOutJobTrigger, scheduler ); } // Complex BlockOut Trigger if ( isBlockOutTriggerComplex ) { return willComplexBlockOutTriggerBlockSchedule( blockOutJobTrigger, scheduleTrigger, scheduler ); } /* * Both blockOut and schedule triggers are simple. Continue with mathematical calculations */ long blockOutRecurrence = getRecurrenceInterval( blockOutJobTrigger ); long scheduleRecurrence = getRecurrenceInterval( scheduleTrigger ); for ( int i = 0; i < 1000; i++ ) { double shiftBy = ( blockOutRecurrence - scheduleRecurrence ) * i / (double) scheduleRecurrence; double x1 = ( blockOutJobTrigger.getStartTime().getTime() - scheduleTrigger.getStartTime().getTime() ) / (double) scheduleRecurrence + shiftBy; double x2 = ( blockOutJobTrigger.getStartTime().getTime() + blockOutJobTrigger.getDuration() - scheduleTrigger .getStartTime().getTime() ) / (double) scheduleRecurrence + shiftBy; if ( hasIntBetween( x1, x2 ) ) { int xShift = (int) Math.ceil( x1 < x2 ? x1 : x2 ); long scheduleDate = scheduleTrigger.getStartTime().getTime() + scheduleRecurrence * ( i + xShift ); long blockOutStartDate = blockOutJobTrigger.getStartTime().getTime() + blockOutRecurrence * i; // Test intersection of dates fall within range if ( scheduleTrigger.getStartTime().getTime() <= scheduleDate && ( scheduleTrigger.getEndTime() == null || scheduleDate <= scheduleTrigger.getEndTime().getTime() ) && blockOutJobTrigger.getStartTime().getTime() <= blockOutStartDate && ( blockOutJobTrigger.getEndTime() == null || blockOutStartDate <= blockOutJobTrigger.getEndTime() .getTime() ) ) { return true; } } } return false; } private static boolean willComplexBlockOutTriggerBlockSchedule( IJobTrigger blockOutJobTrigger, IJobTrigger scheduleTrigger, IScheduler scheduler ) { // Short circuit if schedule trigger after end time of block out trigger if ( ( blockOutJobTrigger.getEndTime() != null && scheduleTrigger.getStartTime().after( blockOutJobTrigger.getEndTime() ) ) || ( scheduleTrigger.getEndTime() != null && blockOutJobTrigger.getStartTime().after( scheduleTrigger.getEndTime() ) ) ) { return false; } long duration = blockOutJobTrigger.getDuration(); // Loop through fire times of block out trigger for ( Date blockOutStartDate : getFireTimes( blockOutJobTrigger, scheduler ) ) { Date blockOutEndDate = new Date( blockOutStartDate.getTime() + duration ); if ( willBlockOutRangeBlockSimpleTrigger( blockOutStartDate, blockOutEndDate, scheduleTrigger, scheduler ) ) { return true; } } return false; } private static boolean willBlockOutRangeBlockSimpleTrigger( Date startBlockOutRange, Date endBlockOutRange, IJobTrigger scheduleTrigger, IScheduler scheduler ) { // ( S1 - S ) / R <= x <= ( S2 - S ) / R double recurrence = getRecurrenceInterval( scheduleTrigger ); recurrence = recurrence != 0 ? recurrence : 1; double x1 = ( startBlockOutRange.getTime() - scheduleTrigger.getStartTime().getTime() ) / recurrence; double x2 = ( endBlockOutRange.getTime() - scheduleTrigger.getStartTime().getTime() ) / recurrence; return hasPositiveIntBetween( x1, x2 ); } private static boolean willBlockComplexScheduleTrigger( IJobTrigger trigger, IJobTrigger blockOut, IScheduler scheduler ) { for ( Date fireTime : getFireTimes( trigger, scheduler ) ) { if ( willBlockDate( blockOut, fireTime, scheduler ) ) { return true; } } return false; } private static boolean willComplexBlockOutBlockComplexScheduleTrigger( IJobTrigger blockOutJobTrigger, IJobTrigger jobTrigger, IScheduler scheduler ) { List<Date> blockOutFireTimes = getFireTimes( blockOutJobTrigger, scheduler ); int iStart = 0; for ( Date scheduleFireTime : getFireTimes( jobTrigger, scheduler ) ) { for ( int i = iStart; i < blockOutFireTimes.size(); i++ ) { Date blockOutStartDate = blockOutFireTimes.get( i ); // BlockOut start date after scheduled fire time if ( blockOutStartDate.after( scheduleFireTime ) ) { iStart = i; break; } Date blockOutEndDate = new Date( blockOutStartDate.getTime() + blockOutJobTrigger.getDuration() ); if ( isDateIncludedInRangeInclusive( blockOutStartDate, blockOutEndDate, scheduleFireTime ) ) { return true; } } } return false; } private static boolean willBlockDate( IJobTrigger blockOutJobTrigger, Date date, IScheduler scheduler ) { // S + Rx <= d <= S + Rx + D // Out of range of block out if ( date.before( blockOutJobTrigger.getStartTime() ) || ( blockOutJobTrigger.getEndTime() != null && date.after( blockOutJobTrigger.getEndTime() ) ) ) { return false; } if ( isComplexTrigger( blockOutJobTrigger ) ) { return willComplexBlockOutTriggerBlockDate( blockOutJobTrigger, getFireTimes( blockOutJobTrigger, scheduler ), date ); } long blockOutRecurrenceInterval = getRecurrenceInterval( blockOutJobTrigger ); double x1 = ( date.getTime() - blockOutJobTrigger.getStartTime().getTime() ) / (double) blockOutRecurrenceInterval; double x2 = ( date.getTime() - ( blockOutJobTrigger.getStartTime().getTime() + blockOutJobTrigger.getDuration() ) ) / (double) blockOutRecurrenceInterval; return hasPositiveIntBetween( x1, x2 ); } private static boolean willComplexBlockOutTriggerBlockDate( IJobTrigger blockOutJobTrigger, List<Date> blockOutDates, Date date ) { // Short circuit if date does not fall within a valid start/end date range if ( date.before( blockOutJobTrigger.getStartTime() ) || ( blockOutJobTrigger.getEndTime() != null && date.after( blockOutJobTrigger.getEndTime() ) ) ) { return false; } long blockOutDuration = blockOutJobTrigger.getDuration(); for ( Date blockOutStartDate : blockOutDates ) { // Block out date has passed the date being tested if ( blockOutStartDate.after( date ) ) { break; } Date blockOutEndDate = new Date( blockOutStartDate.getTime() + blockOutDuration ); // Date falls within inclusive block out range if ( isDateIncludedInRangeInclusive( blockOutStartDate, blockOutEndDate, date ) ) { return true; } } return false; } public static boolean isComplexTrigger( IJobTrigger jobTrigger ) { return jobTrigger instanceof ComplexJobTrigger || jobTrigger instanceof CronJobTrigger; } private static long getRecurrenceInterval( IJobTrigger jobTrigger ) { if ( !isComplexTrigger( jobTrigger ) ) { return ( (SimpleJobTrigger) jobTrigger ).getRepeatInterval() * 1000; // Have to convert to milliseconds } throw new RuntimeException( "Can not get recurrence interval from JobTriggers which are not SimpleJobTrigger" ); //$NON-NLS-1$ } public static List<Date> getFireTimes( IJobTrigger jobTrigger, IScheduler scheduler ) { // Determines the maximum amount of fire times allowed to be calculated int n = 1000; Date startDate = new Date( System.currentTimeMillis() ); Date endDate = new Date( startDate.getTime() + 4 * TIME.YEAR.time ); // Quartz Triggers if ( scheduler instanceof QuartzScheduler ) { try { List<Date> dates = new ArrayList<Date>(); boolean endDateIsNull = jobTrigger.getEndTime() == null; Trigger trigger = QuartzScheduler.createQuartzTrigger( jobTrigger, new QuartzJobKey( "test", "test" ) ); //$NON-NLS-1$ //$NON-NLS-2$ // add previous trigger (it might be currently active) IBlockoutManager manager = PentahoSystem.get( IBlockoutManager.class, "IBlockoutManager", null ); //$NON-NLS-1$; if ( manager != null ) { List<Job> blockouts = manager.getBlockOutJobs(); for ( Job blockout : blockouts ) { if ( blockout.getLastRun() != null ) { dates.add( blockout.getLastRun() ); } } } for ( int i = 0; i < n; i++ ) { Date nextFireTime = trigger.getFireTimeAfter( startDate ); if ( ( nextFireTime == null ) || ( nextFireTime.after( endDate ) || ( !endDateIsNull && nextFireTime.after( jobTrigger.getEndTime() ) ) ) ) { break; } dates.add( nextFireTime ); startDate = nextFireTime; } return dates; } catch ( SchedulerException e ) { throw new RuntimeException( e ); } } throw new RuntimeException( "Can not calculate fire times for unsupported Scheduler Type: " //$NON-NLS-1$ + scheduler.getClass().getSimpleName() ); } public static boolean shouldFireNow( List<IJobTrigger> blockOutJobTriggers, IScheduler scheduler ) { Date currentTime = new Date( System.currentTimeMillis() ); for ( IJobTrigger blockOutJobTrigger : blockOutJobTriggers ) { if ( willBlockDate( blockOutJobTrigger, currentTime, scheduler ) ) { return false; } } return true; } public static boolean isPartiallyBlocked( IJobTrigger scheduleJobTrigger, List<IJobTrigger> blockOutJobTriggers, IScheduler scheduler ) { // Loop through blockout triggers for ( IJobTrigger blockOut : blockOutJobTriggers ) { if ( willBlockSchedule( scheduleJobTrigger, blockOut, scheduler ) ) { return true; } } return false; } /** * @param x1 * double * @param x2 * double * @return whether an {@link Integer} exists between x1 and x2 */ private static boolean hasIntBetween( double x1, double x2 ) { double ceilX = Math.ceil( x1 ); double floorX = Math.floor( x2 ); if ( x1 > x2 ) { ceilX = Math.ceil( x2 ); floorX = Math.floor( x1 ); } return ( floorX - ceilX ) >= 0; } /** * @param x1 * double * @param x2 * double * @return whether there is a positive integer between x1 and x2 */ private static boolean hasPositiveIntBetween( double x1, double x2 ) { return ( x1 < x2 ? x2 >= 0 : x1 >= 0 ) && hasIntBetween( x1, x2 ); } /** * @param dateRangeStart * {@link Date} start of range * @param dateRangeEnd * {@link Date} end of range * @param date * {@link Date} * @return whether the date falls within the date inclusive date range */ private static boolean isDateIncludedInRangeInclusive( Date dateRangeStart, Date dateRangeEnd, Date date ) { long dateTime = date.getTime(); return dateRangeStart.getTime() <= dateTime && dateTime <= dateRangeEnd.getTime(); } }