/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.util.core.task;
import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.elvis;
import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.truth;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.time.Duration;
import com.google.common.annotations.Beta;
import com.google.common.base.Throwables;
/**
* A task which runs with a fixed period.
* <p>
* Note that some termination logic, including {@link #addListener(Runnable, java.util.concurrent.Executor)},
* is not precisely defined.
*/
// TODO ScheduledTask is a very pragmatic implementation; would be nice to tighten,
// reduce external assumptions about internal structure, and clarify "done" semantics
public class ScheduledTask extends BasicTask<Object> {
final Callable<Task<?>> taskFactory;
/**
* Initial delay before running, set as flag in constructor; defaults to 0
*/
protected Duration delay;
/**
* The time to wait between executions, or null if not to repeat (default), set as flag to constructor;
* this may be modified for subsequent submissions by a running task generated by the factory
* using {@link #getSubmittedByTask().setPeriod(Duration)}
*/
protected Duration period = null;
/**
* Optional, set as flag in constructor; defaults to null meaning no limit.
*/
protected Integer maxIterations = null;
/**
* Set false if the task should be rescheduled after throwing an exception; defaults to true.
*/
protected boolean cancelOnException = true;
protected int runCount=0;
protected Task<?> recentRun, nextRun;
Class<? extends Exception> lastThrownType;
public int getRunCount() { return runCount; }
public ScheduledFuture<?> getNextScheduled() { return (ScheduledFuture<?>)internalFuture; }
public ScheduledTask(Callable<Task<?>> taskFactory) {
this(MutableMap.of(), taskFactory);
}
public ScheduledTask(final Task<?> task) {
this(MutableMap.of(), task);
}
public ScheduledTask(Map<?,?> flags, final Task<?> task){
this(flags, new Callable<Task<?>>(){
@Override
public Task<?> call() throws Exception {
return task;
}});
}
public ScheduledTask(Map<?,?> flags, Callable<Task<?>> taskFactory) {
super(flags);
this.taskFactory = taskFactory;
delay = Duration.of(elvis(flags.remove("delay"), 0));
period = Duration.of(elvis(flags.remove("period"), null));
maxIterations = elvis(flags.remove("maxIterations"), null);
Object cancelFlag = flags.remove("cancelOnException");
cancelOnException = cancelFlag == null || Boolean.TRUE.equals(cancelFlag);
}
public ScheduledTask delay(Duration d) {
this.delay = d;
return this;
}
public ScheduledTask delay(long val) {
return delay(Duration.millis(val));
}
public ScheduledTask period(Duration d) {
this.period = d;
return this;
}
public ScheduledTask period(long val) {
return period(Duration.millis(val));
}
public ScheduledTask maxIterations(int val) {
this.maxIterations = val;
return this;
}
public ScheduledTask cancelOnException(boolean cancel) {
this.cancelOnException = cancel;
return this;
}
public Callable<Task<?>> getTaskFactory() {
return taskFactory;
}
public Task<?> newTask() {
try {
return taskFactory.call();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
protected String getActiveTaskStatusString(int verbosity) {
StringBuilder rv = new StringBuilder("Scheduler");
if (runCount > 0) {
rv.append(", iteration ").append(runCount + 1);
}
if (recentRun != null) {
Duration start = Duration.sinceUtc(recentRun.getStartTimeUtc());
rv.append(", last run ").append(start).append(" ago");
}
if (truth(getNextScheduled())) {
Duration untilNext = Duration.millis(getNextScheduled().getDelay(TimeUnit.MILLISECONDS));
if (untilNext.isPositive())
rv.append(", next in ").append(untilNext);
else
rv.append(", next imminent");
}
return rv.toString();
}
@Override
public boolean isDone() {
return isCancelled() || (maxIterations!=null && maxIterations <= runCount) || (period==null && nextRun!=null && nextRun.isDone());
}
public synchronized void blockUntilFirstScheduleStarted() {
// TODO Assumes that maxIterations is not negative!
while (true) {
if (isCancelled()) throw new CancellationException();
if (recentRun==null)
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Throwables.propagate(e);
}
if (recentRun!=null) return;
}
}
public void blockUntilEnded() {
while (!isDone()) super.blockUntilEnded();
}
/** @return The value of the most recently run task */
public Object get() throws InterruptedException, ExecutionException {
blockUntilStarted();
blockUntilFirstScheduleStarted();
return (truth(recentRun)) ? recentRun.get() : internalFuture.get();
}
@Override
protected boolean doCancel(org.apache.brooklyn.util.core.task.TaskInternal.TaskCancellationMode mode) {
if (nextRun!=null) {
((TaskInternal<?>)nextRun).cancel(mode);
}
return super.doCancel(mode);
}
/**
* Internal method used to allow callers to wait for underlying tasks to finished in the case of cancellation.
* @param timeout maximum time to wait
*/
@Beta
public boolean blockUntilNextRunFinished(Duration timeout) {
return Tasks.blockUntilInternalTasksEnded(nextRun, timeout);
}
}