com.github.kagkarlsson.scheduler.SchedulerClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of db-scheduler Show documentation
Show all versions of db-scheduler Show documentation
Simple persistent scheduler for scheduled tasks, recurring or ad-hoc.
/**
* Copyright (C) Gustav Karlsson
*
* 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 com.github.kagkarlsson.scheduler;
import com.github.kagkarlsson.scheduler.exceptions.TaskInstanceNotFoundException;
import com.github.kagkarlsson.scheduler.exceptions.TaskInstanceCurrentlyExecutingException;
import com.github.kagkarlsson.scheduler.jdbc.DefaultJdbcCustomization;
import com.github.kagkarlsson.scheduler.jdbc.JdbcCustomization;
import com.github.kagkarlsson.scheduler.jdbc.JdbcTaskRepository;
import com.github.kagkarlsson.scheduler.serializer.Serializer;
import com.github.kagkarlsson.scheduler.stats.StatsRegistry;
import com.github.kagkarlsson.scheduler.task.Execution;
import com.github.kagkarlsson.scheduler.task.SchedulableInstance;
import com.github.kagkarlsson.scheduler.task.Task;
import com.github.kagkarlsson.scheduler.task.TaskInstance;
import com.github.kagkarlsson.scheduler.task.TaskInstanceId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import static java.util.Optional.ofNullable;
public interface SchedulerClient {
/**
* Schedule a new execution.
*
* @param taskInstance Task-instance, optionally with data
* @param executionTime Instant it should run
* @return void
* @see java.time.Instant
* @see com.github.kagkarlsson.scheduler.task.TaskInstance
*/
void schedule(TaskInstance taskInstance, Instant executionTime);
void schedule(SchedulableInstance schedulableInstance);
/**
* Update an existing execution to a new execution-time. If the execution does not exist or if it is currently
* running, an exception is thrown.
*
* @param taskInstanceId
* @param newExecutionTime the new execution-time
* @see java.time.Instant
* @see com.github.kagkarlsson.scheduler.task.TaskInstanceId
*/
void reschedule(TaskInstanceId taskInstanceId, Instant newExecutionTime);
/**
* Update an existing execution with a new execution-time and new task-data. If the execution does not exist or if
* it is currently running, an exception is thrown.
*
* @param taskInstanceId
* @param newExecutionTime the new execution-time
* @param newData the new task-data
* @see java.time.Instant
* @see com.github.kagkarlsson.scheduler.task.TaskInstanceId
*/
void reschedule(TaskInstanceId taskInstanceId, Instant newExecutionTime, T newData);
/**
* Update an existing execution with a new execution-time and new task-data. If the execution does not exist or if
* it is currently running, an exception is thrown.
*
* @param schedulableInstance the updated instance
*/
void reschedule(SchedulableInstance schedulableInstance);
/**
* Removes/Cancels an execution.
*
* @param taskInstanceId
* @return void
* @see com.github.kagkarlsson.scheduler.task.TaskInstanceId
*/
void cancel(TaskInstanceId taskInstanceId);
/**
* Gets all scheduled executions and supplies them to the provided Consumer. A Consumer is used to
* avoid forcing the SchedulerClient to load all executions in memory. Currently running executions are not returned.
*
* @param consumer Consumer for the executions
* @return void
*/
void fetchScheduledExecutions(Consumer> consumer);
void fetchScheduledExecutions(ScheduledExecutionsFilter filter, Consumer> consumer);
/**
* @see #fetchScheduledExecutions(Consumer)
*/
default List> getScheduledExecutions() {
List> executions = new ArrayList<>();
fetchScheduledExecutions(executions::add);
return executions;
}
/**
* @see #fetchScheduledExecutions(Consumer)
*/
default List> getScheduledExecutions(ScheduledExecutionsFilter filter) {
List> executions = new ArrayList<>();
fetchScheduledExecutions(filter, executions::add);
return executions;
}
/**
* Gets all scheduled executions for a task and supplies them to the provided Consumer. A Consumer is used to
* avoid forcing the SchedulerClient to load all executions in memory. Currently running executions are not returned.
*
* @param taskName the name of the task to get scheduled-executions for
* @param dataClass the task data-class the data will be serialized and cast to
* @param consumer Consumer for the executions
* @return void
*/
void fetchScheduledExecutionsForTask(String taskName, Class dataClass, Consumer> consumer);
void fetchScheduledExecutionsForTask(String taskName, Class dataClass, ScheduledExecutionsFilter filter, Consumer> consumer);
/**
* @see #fetchScheduledExecutionsForTask(String, Class, Consumer)
*/
default List> getScheduledExecutionsForTask(String taskName, Class dataClass) {
List> executions = new ArrayList<>();
fetchScheduledExecutionsForTask(taskName, dataClass, executions::add);
return executions;
}
/**
* @see #fetchScheduledExecutionsForTask(String, Class, Consumer)
*/
default List> getScheduledExecutionsForTask(String taskName, Class dataClass, ScheduledExecutionsFilter filter) {
List> executions = new ArrayList<>();
fetchScheduledExecutionsForTask(taskName, dataClass, filter, executions::add);
return executions;
}
/**
* Gets the details for a specific scheduled execution. Currently running executions are also returned.
*
* @param taskInstanceId
* @return Optional.empty() if no matching execution found
* @see com.github.kagkarlsson.scheduler.task.TaskInstanceId
* @see com.github.kagkarlsson.scheduler.ScheduledExecution
*/
Optional> getScheduledExecution(TaskInstanceId taskInstanceId);
class Builder {
private final DataSource dataSource;
private List> knownTasks;
private Serializer serializer = Serializer.DEFAULT_JAVA_SERIALIZER;
private String tableName = JdbcTaskRepository.DEFAULT_TABLE_NAME;
private JdbcCustomization jdbcCustomization;
private Builder(DataSource dataSource, List> knownTasks) {
this.dataSource = dataSource;
this.knownTasks = knownTasks;
}
public static Builder create(DataSource dataSource, Task>... knownTasks) {
return new Builder(dataSource, Arrays.asList(knownTasks));
}
public static Builder create(DataSource dataSource, List> knownTasks) {
return new Builder(dataSource, knownTasks);
}
public Builder serializer(Serializer serializer) {
this.serializer = serializer;
return this;
}
public Builder tableName(String tableName) {
this.tableName = tableName;
return this;
}
public Builder jdbcCustomization(JdbcCustomization jdbcCustomization) {
this.jdbcCustomization = jdbcCustomization;
return this;
}
public SchedulerClient build() {
TaskResolver taskResolver = new TaskResolver(StatsRegistry.NOOP, knownTasks);
final SystemClock clock = new SystemClock();
TaskRepository taskRepository = new JdbcTaskRepository(
dataSource,
false,
ofNullable(jdbcCustomization).orElse(new DefaultJdbcCustomization()),
tableName,
taskResolver,
new SchedulerClientName(),
serializer,
clock);
return new StandardSchedulerClient(taskRepository, clock);
}
}
class StandardSchedulerClient implements SchedulerClient {
private static final Logger LOG = LoggerFactory.getLogger(StandardSchedulerClient.class);
protected final TaskRepository taskRepository;
private final Clock clock;
private SchedulerClientEventListener schedulerClientEventListener;
StandardSchedulerClient(TaskRepository taskRepository, Clock clock) {
this(taskRepository, SchedulerClientEventListener.NOOP, clock);
}
StandardSchedulerClient(TaskRepository taskRepository, SchedulerClientEventListener schedulerClientEventListener, Clock clock) {
this.taskRepository = taskRepository;
this.schedulerClientEventListener = schedulerClientEventListener;
this.clock = clock;
}
@Override
public void schedule(TaskInstance taskInstance, Instant executionTime) {
boolean success = taskRepository.createIfNotExists(SchedulableInstance.of(taskInstance, executionTime));
if (success) {
notifyListeners(ClientEvent.EventType.SCHEDULE, taskInstance, executionTime);
}
}
@Override
public void schedule(SchedulableInstance schedulableInstance) {
schedule(schedulableInstance.getTaskInstance(), schedulableInstance.getNextExecutionTime(clock.now()));
}
@Override
public void reschedule(TaskInstanceId taskInstanceId, Instant newExecutionTime) {
reschedule(taskInstanceId, newExecutionTime, null);
}
@Override
public void reschedule(SchedulableInstance schedulableInstance) {
reschedule(schedulableInstance, schedulableInstance.getNextExecutionTime(clock.now()), schedulableInstance.getTaskInstance().getData());
}
@Override
public void reschedule(TaskInstanceId taskInstanceId, Instant newExecutionTime, T newData) {
String taskName = taskInstanceId.getTaskName();
String instanceId = taskInstanceId.getId();
Optional execution = taskRepository.getExecution(taskName, instanceId);
if (execution.isPresent()) {
if (execution.get().isPicked()) {
throw new TaskInstanceCurrentlyExecutingException(taskName, instanceId);
}
boolean success;
if (newData == null) {
success = taskRepository.reschedule(execution.get(), newExecutionTime, null, null, 0);
} else {
success = taskRepository.reschedule(execution.get(), newExecutionTime, newData, null, null, 0);
}
if (success) {
notifyListeners(ClientEvent.EventType.RESCHEDULE, taskInstanceId, newExecutionTime);
}
} else {
throw new TaskInstanceNotFoundException(taskName, instanceId);
}
}
@Override
public void cancel(TaskInstanceId taskInstanceId) {
String taskName = taskInstanceId.getTaskName();
String instanceId = taskInstanceId.getId();
Optional execution = taskRepository.getExecution(taskName, instanceId);
if (execution.isPresent()) {
if (execution.get().isPicked()) {
throw new TaskInstanceCurrentlyExecutingException(taskName, instanceId);
}
taskRepository.remove(execution.get());
notifyListeners(ClientEvent.EventType.CANCEL, taskInstanceId, execution.get().executionTime);
} else {
throw new TaskInstanceNotFoundException(taskName, instanceId);
}
}
@Override
public void fetchScheduledExecutions(Consumer> consumer) {
fetchScheduledExecutions(ScheduledExecutionsFilter.all().withPicked(false), consumer);
}
@Override
public void fetchScheduledExecutions(ScheduledExecutionsFilter filter, Consumer> consumer) {
taskRepository.getScheduledExecutions(filter,
execution -> consumer.accept(new ScheduledExecution<>(Object.class, execution)));
}
@Override
public void fetchScheduledExecutionsForTask(String taskName, Class dataClass, Consumer> consumer) {
fetchScheduledExecutionsForTask(taskName, dataClass, ScheduledExecutionsFilter.all().withPicked(false), consumer);
}
@Override
public void fetchScheduledExecutionsForTask(String taskName, Class dataClass, ScheduledExecutionsFilter filter,
Consumer> consumer) {
taskRepository.getScheduledExecutions(filter, taskName,
execution -> consumer.accept(new ScheduledExecution<>(dataClass, execution)));
}
@Override
public Optional> getScheduledExecution(TaskInstanceId taskInstanceId) {
Optional e = taskRepository.getExecution(taskInstanceId.getTaskName(), taskInstanceId.getId());
return e.map(oe -> new ScheduledExecution<>(Object.class, oe));
}
private void notifyListeners(ClientEvent.EventType eventType, TaskInstanceId taskInstanceId, Instant executionTime) {
try {
schedulerClientEventListener.newEvent(new ClientEvent(new ClientEvent.ClientEventContext(eventType, taskInstanceId, executionTime)));
} catch (Exception e) {
LOG.error("Error when notifying SchedulerClientEventListener.", e);
}
}
}
class SchedulerClientName implements SchedulerName {
@Override
public String getName() {
return "SchedulerClient";
}
}
}