diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/IEnumWithLongValue.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/IEnumWithLongValue.java new file mode 100644 index 000000000..3e46ccc59 --- /dev/null +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/IEnumWithLongValue.java @@ -0,0 +1,81 @@ +package ru.spcex.clearing.scheduler.enums; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Удобный интерфейс для enum при использовании TextErrorService. + */ +public interface IEnumWithLongValue extends Serializable { + + + /** + * Проверяет что среди данного набора Enum, присутствует элемент с данным id + * id может быть null + */ + static & IEnumWithLongValue> boolean contains(Long id, T... enumSet) { + for (T e : enumSet) { + if (Objects.equals(id, e.getId())) + return true; + } + return false; + } + + static & IEnumWithLongValue> boolean contains(T e, T... enumSet) { + for (T enumEl : enumSet) { + if (enumEl.equals(e)) + return true; + } + return false; + } + + static & IEnumWithLongValue> Long[] toLongArray(T... enumSet) { + Long[] enumId = new Long[enumSet.length]; + for (int i = 0; i < enumSet.length; i++) + enumId[i] = enumSet[i].getId(); + return enumId; + } + + /** + * Проверяет что среди всех Enum данного класса, присутствует элемент с данным id + * id может быть null + */ + static & IEnumWithLongValue> boolean contains(Class enumClass, Long id) { + for (T e : enumClass.getEnumConstants()) { + if (Objects.equals(id, e.getId())) + return true; + } + return false; + } + + + /** + * Возвращает Enum по id, если в заданном классе такой определен + * id может быть null + * + * @return Enum если нашел, иначе null + */ + static & IEnumWithLongValue> T getEnumById(Class enumClass, Long id) { + for (T e : enumClass.getEnumConstants()) { + if (Objects.equals(id, e.getId())) + return e; + } + return null; + } + + static Long getIdOrNull(IEnumWithLongValue enumVal) { + return enumVal == null ? null : enumVal.getId(); + } + + + /** + * errorCode + **/ + Long getId(); + + default boolean equalsById(Long id) { + return id != null && getId().equals(id); + } + + String elementName(); +} diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/TaskStatuses.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/TaskStatuses.java new file mode 100644 index 000000000..ab2709ba8 --- /dev/null +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/TaskStatuses.java @@ -0,0 +1,31 @@ +package ru.spcex.clearing.scheduler.enums; + +import java.util.Objects; + +public enum TaskStatuses { + ACTIVE("ACTV"), + BLOCKED("CNCL"), + CANCEL("BLKD"); + + private final String name; + + TaskStatuses(String name) { + this.name = name; + } + + static TaskStatuses getEnumById(String name) { + for (TaskStatuses e : TaskStatuses.values()) { + if (Objects.equals(name, e.name())) + return e; + } + return null; + } + + public String getName() { + return this.name; + } + + public String elementName() { + return this.name(); + } +} diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/Tasks.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/Tasks.java new file mode 100644 index 000000000..7a5ec165b --- /dev/null +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/enums/Tasks.java @@ -0,0 +1,20 @@ +package ru.spcex.clearing.scheduler.enums; + +public enum Tasks implements IEnumWithLongValue { + SOME_STATUS(1L);//TODO CLEARIFY + + private final Long id; + + Tasks(Long id) { + this.id = id; + } + + public Long getId() { + return this.id; + } + + public String elementName() { + return this.name(); + } + +} diff --git a/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TaskManager.java b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TaskManager.java new file mode 100644 index 000000000..7e226ffd5 --- /dev/null +++ b/clearing-parent/scheduler-service/src/main/java/ru/spcex/clearing/scheduler/service/TaskManager.java @@ -0,0 +1,219 @@ +package ru.spcex.clearing.scheduler.service; + +import com.hazelcast.core.EntryEvent; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IMap; +import com.hazelcast.map.listener.EntryAddedListener; +import com.hazelcast.map.listener.EntryRemovedListener; +import com.hazelcast.map.listener.EntryUpdatedListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.scheduling.TaskScheduler; +import ru.clearing.classes.statics.data.scheduler.PlannerAllToday; +import ru.spcex.clearing.scheduler.enums.Tasks; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +import static ru.spcex.clearing.imdg.IMDGDistributedNames.Map_PlannerAllToday; + +/** + * Планировщик задач, расписание берёт из Hazelcast map. + *

+ */ +public abstract class TaskManager implements EntryAddedListener, + EntryUpdatedListener, EntryRemovedListener, + InitializingBean { + private static final Logger log = LoggerFactory.getLogger(TaskManager.class); + + protected HazelcastInstance hazelcastInstance; + protected IMap plannerAllTodayMapStore; + protected TaskScheduler taskScheduler; + + protected ConcurrentHashMap scheduledJobs; + + protected TaskManager(TaskScheduler taskScheduler, HazelcastInstance hazelcastInstance) { + this.taskScheduler = taskScheduler; + this.hazelcastInstance = hazelcastInstance; + } + + private static LocalDateTime dateOldTypeConvert(Date oldDate) { + return LocalDateTime.ofInstant(oldDate.toInstant(), ZoneId.systemDefault()); + } + + private static LocalDate dateTypeConvert(Date oldDate) { + return dateOldTypeConvert(oldDate).toLocalDate(); + } + + private static LocalTime timeTypeConvert(Date oldDate) { + return dateOldTypeConvert(oldDate).toLocalTime(); + } + + @Override + public void afterPropertiesSet() { + plannerAllTodayMapStore = hazelcastInstance.getMap(Map_PlannerAllToday); + scheduledJobs = new ConcurrentHashMap<>(); + updateScheduler(); + } + + // --- Слушатели Hazelcast Map --- + @Override + public void entryAdded(EntryEvent event) { + PlannerAllToday task = event.getValue(); + + + //todo define how to get task status Tasks taskType = getEnumById(Tasks.class, task.getParentId()); +// if (taskType == null) { +// log.warn("Task skipped, {} task type not recognized", task.getTaskId()); +// return; +// } + processTask(task); + } + + @Override + public void entryUpdated(EntryEvent event) { + PlannerAllToday task = event.getValue(); + PlannerAllToday oldTask = event.getOldValue(); + + LocalTime oldTime = oldTask.getTaskTime(); +// todo define TaskStatuses oldStatus + //если таск относится к другому обработчику, пропускаем +// if (!getTaskId().equalsById(task.getTaskId()) && !getTaskId().equalsById(oldTask.getTaskId())) { +// return; +// } +// if (!Objects.equals(task.getTaskId(), oldTask.getTaskId())) { +// throw new IllegalStateException("changed taskId for SchedulerAllToday in core"); +// } +// if (ACTIVE.equalsById(oldTask.getTaskStatusId())) { //&& ACTIVE.equalsById(task.getTaskStatusId()) +// if (!removeTask(oldTime)) { +// log.debug("cannot cancel task with type {}, time {}", getTaskId().name(), oldTime.toString()); +// } else { +// log.debug("task with type {}, time {} execution cancelled, adding altered task...", getTaskId().name(), oldTime.toString()); +// } +// processTask(task); +// } else if (CANCEL.equalsById(oldTask.getTaskStatusId())) { //&& TaskStatuses.CANCEL.equalsById(task.getTaskStatusId()) +// restorePreviouslyRemovedTask(oldTime, oldTask); +// processTask(task); +// } else if (BLOCKED.equalsById(oldTask.getTaskStatusId())) { +// processTask(task); +// } + } + + @Override + public void entryRemoved(EntryEvent event) { + PlannerAllToday taskToRemove = event.getOldValue(); + if (taskToRemove == null) { + log.debug("no task in removed event"); + return; + } + //LocalTime removedTaskTime = timeTypeConvert(taskToRemove.getTaskTime()); + // + //if (ACTIVE.equalsById(taskToRemove.getTaskStatusId())) { + // if (removeTask(removedTaskTime)) + // log.debug("task successfully canceled"); + //} else if (CANCEL.equalsById(taskToRemove.getTaskStatusId())) { + // restorePreviouslyRemovedTask(removedTaskTime, taskToRemove); + //} else if (BLOCKED.equalsById(taskToRemove.getTaskStatusId())) { + // log.debug("BLOCKED task removed; do nothing"); + //} + } + + private void restorePreviouslyRemovedTask(LocalTime oldTime, PlannerAllToday oldTask) { + ScheduledFuture cancelledFuture = scheduledJobs.get(oldTime); + if (cancelledFuture != null && cancelledFuture.isCancelled()) { + PlannerAllToday schedulerAllToday = new PlannerAllToday(); + // schedulerAllToday.setTaskId(getTaskId().getId()); + // schedulerAllToday.setTaskStatusId(ACTIVE.getId()); + schedulerAllToday.setTaskTime(oldTask.getTaskTime()); + log.debug("CANCEL task updated/removed; restoring previously cancelled task"); + processTask(schedulerAllToday); + } + } + + // --- Планирование задач --- + protected void updateScheduler() { + //Collection schedulerAllTodays = plannerAllTodayMapStore.values(); + //Collection sortedSchedulers = + // schedulerAllTodays.stream().sorted((o1, o2) -> (ACTIVE.equalsById(o1.getTaskStatusId()) && CANCEL.equalsById(o2.getTaskStatusId())) ? -1 : 0) + // .collect(Collectors.toCollection(ArrayList::new)); + //for (PlannerAllToday schedulerAllToday : sortedSchedulers) { + // processTask(schedulerAllToday); + //} + } + + // --- Работа с задачами --- + private void processTask(PlannerAllToday task) { + //LocalTime taskTime = task.getTaskTime(); + //Tasks taskType = getEnumById(Tasks.class, task.getTaskId()); + //TaskStatuses taskStatus = getEnumById(TaskStatuses.class, task.getTaskStatusId()); + //if (taskStatus == null) throw new IllegalStateException("task status from core can't be null"); + //if (!getTaskId().equals(taskType)) { + // log.debug("Task skipped - taskTime {}, is not of acceptable type {}", taskTime.toString(), taskType != null ? taskType.name() : ""); + // return; + //} + //if (taskTime.isBefore(LocalTime.now())) { + // log.debug("Task skipped - taskTime {} is before now, taskType {} ok", taskTime, getTaskId().toString()); + // return; + //} + //if (taskStatus.equals(BLOCKED)) { + // log.debug("Task skipped - timeTime {} with status {}", taskTime.toString(), BLOCKED.toString()); + // return; + //} + //{ + // ScheduledFuture future = scheduledJobs.get(taskTime); + // if (future != null) { + // if (TaskStatuses.CANCEL.equals(taskStatus)) { + // //случай когда пришел cancel, пытаемся отменить зарегистрированный ранее таск + // future.cancel(false); + // log.debug("cancelling task time {}", taskTime); + // return; + // } else if (TaskStatuses.ACTIVE.equals(taskStatus)) { + // //случай когда пришел активный таск, и уже был на это время неотмененный + // if (!future.isCancelled()) { + // log.debug("such task time {} has already been registered", taskTime); + // return; + // } + // } + // } + //} + //if (TaskStatuses.CANCEL.equals(taskStatus)) { + // log.debug("task type {} time {} with status CANCEL - no tasks to cancel found", taskType.name(), taskTime); + // return; + //} + ////пришел активный таск + //log.debug("adding task type {}, time {}", taskType.name(), taskTime); + //ScheduledFuture future = taskScheduler.schedule(() -> doJob(task), LocalDateTime.of(LocalDate.now(), taskTime).atZone(ZoneId.systemDefault()).toInstant()); + //ScheduledFuture oldFuture = scheduledJobs.put(taskTime, future); + //if (oldFuture != null && !oldFuture.isCancelled()) { //for synchronization, never + // log.warn("tasks were added simultaneously, cancel former one"); + // boolean success = oldFuture.cancel(false); + // log.warn("cancelling task " + (success ? "success" : "fail")); + //} + } + + private boolean removeTask(LocalTime taskTime) { + ScheduledFuture future = scheduledJobs.remove(taskTime); + if (future == null) { + log.debug("can't cancel task type {}, time {}, not found", getTaskId().name(), taskTime); + return false; + } + return future.cancel(false); + } + + // --- Реализация выполнения задач --- + + /** + * Рабочий тип запланированных задач. + * + * @return идентификатор для фильтра типов планировщика задачь. Планировать задачи только этого типа. + */ + protected abstract Tasks getTaskId(); + + protected abstract void doJob(PlannerAllToday taskInfo); +}