/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.pattern.observer; import com.espertech.esper.core.service.EPStatementHandleCallback; import com.espertech.esper.core.service.EngineLevelExtensionServicesContext; import com.espertech.esper.epl.datetime.calop.CalendarOpPlusFastAddHelper; import com.espertech.esper.epl.datetime.calop.CalendarOpPlusFastAddResult; import com.espertech.esper.epl.datetime.calop.CalendarOpPlusMinus; import com.espertech.esper.epl.expression.time.TimeAbacus; import com.espertech.esper.metrics.instrumentation.InstrumentationHelper; import com.espertech.esper.pattern.MatchedEventMap; import com.espertech.esper.schedule.ScheduleHandleCallback; import com.espertech.esper.schedule.SchedulingService; import java.util.Calendar; /** * Observer implementation for indicating that a certain time arrived, similar to "crontab". */ public class TimerScheduleObserver implements EventObserver, ScheduleHandleCallback { protected final long scheduleSlot; protected MatchedEventMap beginState; protected final ObserverEventEvaluator observerEventEvaluator; private final TimerScheduleSpec spec; private final boolean isFilterChildNonQuitting; // we always keep the anchor time, which could be engine time or the spec time, and never changes in computations protected Calendar anchorTime; protected long anchorRemainder; // for fast computation, keep some last-value information around for the purpose of caching protected boolean isTimerActive = false; private Calendar cachedLastScheduled; private long cachedCountRepeated = 0; protected EPStatementHandleCallback scheduleHandle; public TimerScheduleObserver(TimerScheduleSpec spec, MatchedEventMap beginState, ObserverEventEvaluator observerEventEvaluator, boolean isFilterChildNonQuitting) { this.beginState = beginState; this.observerEventEvaluator = observerEventEvaluator; this.scheduleSlot = observerEventEvaluator.getContext().getPatternContext().getScheduleBucket().allocateSlot(); this.spec = spec; this.isFilterChildNonQuitting = isFilterChildNonQuitting; } public MatchedEventMap getBeginState() { return beginState; } public final void scheduledTrigger(EngineLevelExtensionServicesContext engineLevelExtensionServicesContext) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qPatternObserverScheduledEval(); } // compute reschedule time isTimerActive = false; SchedulingService schedulingService = observerEventEvaluator.getContext().getPatternContext().getSchedulingService(); long nextScheduledTime = computeNextSetLastScheduled(schedulingService.getTime(), observerEventEvaluator.getContext().getStatementContext().getTimeAbacus()); boolean quit = !isFilterChildNonQuitting || nextScheduledTime == -1; observerEventEvaluator.observerEvaluateTrue(beginState, quit); // handle no more invocations planned if (nextScheduledTime == -1) { stopObserve(); observerEventEvaluator.observerEvaluateFalse(false); if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternObserverScheduledEval(); } return; } schedulingService.add(nextScheduledTime, scheduleHandle, scheduleSlot); isTimerActive = true; if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternObserverScheduledEval(); } } public void startObserve() { if (isTimerActive) { throw new IllegalStateException("Timer already active"); } SchedulingService schedulingService = observerEventEvaluator.getContext().getPatternContext().getSchedulingService(); TimeAbacus timeAbacus = observerEventEvaluator.getContext().getStatementContext().getTimeAbacus(); if (anchorTime == null) { if (spec.getOptionalDate() == null) { anchorTime = Calendar.getInstance(observerEventEvaluator.getContext().getStatementContext().getEngineImportService().getTimeZone()); anchorRemainder = timeAbacus.calendarSet(schedulingService.getTime(), anchorTime); } else { anchorTime = spec.getOptionalDate(); anchorRemainder = spec.getOptionalRemainder() == null ? 0 : spec.getOptionalRemainder(); } } long nextScheduledTime = computeNextSetLastScheduled(schedulingService.getTime(), timeAbacus); if (nextScheduledTime == -1) { stopObserve(); observerEventEvaluator.observerEvaluateFalse(false); return; } scheduleHandle = new EPStatementHandleCallback(observerEventEvaluator.getContext().getAgentInstanceContext().getEpStatementAgentInstanceHandle(), this); schedulingService.add(nextScheduledTime, scheduleHandle, scheduleSlot); isTimerActive = true; } public void stopObserve() { if (isTimerActive) { observerEventEvaluator.getContext().getPatternContext().getSchedulingService().remove(scheduleHandle, scheduleSlot); } isTimerActive = false; scheduleHandle = null; cachedCountRepeated = Long.MAX_VALUE; cachedLastScheduled = null; anchorTime = null; } public void accept(EventObserverVisitor visitor) { visitor.visitObserver(beginState, 2, scheduleSlot, spec, anchorTime, cachedCountRepeated, cachedLastScheduled, isTimerActive); } private long computeNextSetLastScheduled(long currentTime, TimeAbacus timeAbacus) { // handle already-stopped if (cachedCountRepeated == Long.MAX_VALUE) { return -1; } // handle date-only-form: "<date>" if (spec.getOptionalRepeatCount() == null && spec.getOptionalDate() != null && spec.getOptionalTimePeriod() == null) { cachedCountRepeated = Long.MAX_VALUE; long computed = timeAbacus.calendarGet(anchorTime, anchorRemainder); if (computed > currentTime) { return computed - currentTime; } return -1; } // handle period-only-form: "P<period>" // handle partial-form-2: "<date>/<period>" (non-recurring) if (spec.getOptionalRepeatCount() == null && spec.getOptionalTimePeriod() != null) { cachedCountRepeated = Long.MAX_VALUE; cachedLastScheduled = (Calendar) anchorTime.clone(); CalendarOpPlusMinus.action(cachedLastScheduled, 1, spec.getOptionalTimePeriod()); long computed = timeAbacus.calendarGet(cachedLastScheduled, anchorRemainder); if (computed > currentTime) { return computed - currentTime; } return -1; } // handle partial-form-1: "R<?>/<period>" // handle full form if (cachedLastScheduled == null) { cachedLastScheduled = (Calendar) anchorTime.clone(); if (spec.getOptionalDate() != null) { cachedCountRepeated = 1; } } CalendarOpPlusFastAddResult nextDue = CalendarOpPlusFastAddHelper.computeNextDue(currentTime, spec.getOptionalTimePeriod(), cachedLastScheduled, timeAbacus, anchorRemainder); if (spec.getOptionalRepeatCount() == -1) { cachedLastScheduled = nextDue.getScheduled(); long computed = timeAbacus.calendarGet(cachedLastScheduled, anchorRemainder); return computed - currentTime; } cachedCountRepeated += nextDue.getFactor(); if (cachedCountRepeated <= spec.getOptionalRepeatCount()) { cachedLastScheduled = nextDue.getScheduled(); long computed = timeAbacus.calendarGet(cachedLastScheduled, anchorRemainder); if (computed > currentTime) { return computed - currentTime; } } cachedCountRepeated = Long.MAX_VALUE; return -1; } }