/*
* Copyright 2012 Research Studios Austria Forschungsges.m.b.H.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package won.bot.framework.eventbot.action.impl.trigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import won.bot.framework.eventbot.EventListenerContext;
import won.bot.framework.eventbot.action.BaseEventBotAction;
import won.bot.framework.eventbot.event.Event;
import won.bot.framework.eventbot.listener.EventListener;
import won.bot.framework.eventbot.listener.impl.ActionOnFirstEventListener;
import java.time.Duration;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Publishes BotTriggerEvents on the eventBus at configurable intervals.
* Upon call to activate() the BotTrigger registers an event listener for the StartBotTrigger event and the StopBotTriggerEvent
* After receiving the StopBotTriggerEvent, the BotTrigger unregisters all event listeners.
*/
public class BotTrigger {
private EventListenerContext context;
private volatile Duration interval;
private AtomicBoolean active = new AtomicBoolean(false);
private EventListener startListener;
private EventListener stopListener;
private volatile ScheduledFuture<?> cancelableTask;
public BotTrigger(EventListenerContext context, Duration interval) {
this.context = context;
this.interval = interval;
}
/**
* Sets the interval to the specified duration and reschedules executions.
*
* @param interval
*/
public void changeIntervalTo(Duration interval) {
this.interval = interval;
reschedule();
}
public Duration getInterval() {
return interval;
}
/**
* Changes the interval by the specified percentage, or sets it to the specified maxInterval, whichever is smaller.
*
* @param factor
*/
public void changeIntervalByFactor(double factor, Duration maxInterval) {
long millis = this.interval.toMillis();
if (millis == 0 && factor < 1) {
//nothing to do, the interval is already minimial
return;
} else if (factor == 1){
//nothing to do, no need to reschedule
return;
}
if (factor <= 0) throw new IllegalArgumentException("factor must be > 0");
long newMillis = (long) ((double) millis * factor);
if (newMillis == millis) {
//for some reason, there was no change. increase/decreaese by 1
newMillis += factor > 1 ? 1 : -1;
}
newMillis = Math.max(0, Math.min(newMillis, maxInterval.toMillis()));
this.interval = Duration.ofMillis(newMillis);
reschedule();
}
public void changeIntervalByFactor(double factor) {
changeIntervalByFactor(factor, Duration.ofSeconds(10));
}
public synchronized void activate() {
if (active.get()) return;
//make the stop listener
this.stopListener = new ActionOnFirstEventListener(this.context, new BotTriggerFilter(this), new BaseEventBotAction(BotTrigger.this.context) {
@Override
protected void doRun(Event event, EventListener executingListener) throws Exception {
//unregister all listeners
BotTrigger.this.context.getEventBus().unsubscribe(BotTrigger.this.startListener);
BotTrigger.this.context.getEventBus().unsubscribe(BotTrigger.this.stopListener);
BotTrigger.this.cancelableTask.cancel(true);
BotTrigger.this.active.set(false);
}
});
//make the start listener
this.startListener = new ActionOnFirstEventListener(this.context, new BotTriggerFilter(this), new BaseEventBotAction(BotTrigger.this.context) {
@Override
protected void doRun(Event event, EventListener executingListener) throws Exception {
reschedule();
}
});
//register both listeners
context.getEventBus().subscribe(StopBotTriggerCommandEvent.class, stopListener);
context.getEventBus().subscribe(StartBotTriggerCommandEvent.class, startListener);
active.set(true);
}
private synchronized void reschedule() {
if (cancelableTask != null) {
cancelableTask.cancel(true);
}
//make the trigger
PeriodicTrigger myTrigger = new PeriodicTrigger(getInterval().toMillis());
myTrigger.setInitialDelay(getInterval().toMillis());
//schedule a task that publishes the BotTriggerEvent
BotTrigger.this.cancelableTask = context.getTaskScheduler().schedule(new Runnable() {
@Override
public void run() {
BotTrigger.this.context.getEventBus().publish(new BotTriggerEvent(BotTrigger.this));
}
}, myTrigger);
}
}