/* * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * 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 org.jbpm.services.task.impl; import java.io.Serializable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.drools.core.common.InternalWorkingMemory; import org.drools.core.time.Job; import org.drools.core.time.JobContext; import org.drools.core.time.JobHandle; import org.drools.core.time.TimerService; import org.drools.core.time.Trigger; import org.drools.core.time.impl.IntervalTrigger; import org.jbpm.process.core.timer.NamedJobContext; import org.jbpm.process.core.timer.TimerServiceRegistry; import org.jbpm.process.core.timer.impl.GlobalTimerService; import org.jbpm.services.task.commands.ExecuteDeadlinesCommand; import org.jbpm.services.task.commands.InitDeadlinesCommand; import org.jbpm.services.task.deadlines.NotificationListener; import org.jbpm.services.task.utils.ClassUtil; import org.kie.api.runtime.CommandExecutor; import org.kie.api.runtime.manager.RuntimeEngine; import org.kie.api.runtime.manager.RuntimeManager; import org.kie.api.task.model.Task; import org.kie.internal.runtime.manager.RuntimeManagerRegistry; import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext; import org.kie.internal.task.api.TaskDeadlinesService; import org.kie.internal.task.api.TaskPersistenceContext; import org.kie.internal.task.api.model.Deadline; import org.kie.internal.task.api.model.DeadlineSummary; import org.kie.internal.task.api.model.Deadlines; import org.kie.internal.task.api.model.InternalTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TaskDeadlinesServiceImpl implements TaskDeadlinesService { private static final Logger logger = LoggerFactory.getLogger(TaskDeadlinesServiceImpl.class); // static instance so it can be used from background jobs protected static volatile CommandExecutor instance; protected static NotificationListener notificationListener; // use single ThreadPoolExecutor for all instances of task services within same JVM private volatile static ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(3); private volatile static Map<Long, List<ScheduledFuture<ScheduledTaskDeadline>>> startScheduledTaskDeadlines = new ConcurrentHashMap<Long, List<ScheduledFuture<ScheduledTaskDeadline>>>(); private volatile static Map<Long, List<ScheduledFuture<ScheduledTaskDeadline>>> endScheduledTaskDeadlines = new ConcurrentHashMap<Long, List<ScheduledFuture<ScheduledTaskDeadline>>>(); private volatile static Map<String, JobHandle> jobHandles = new ConcurrentHashMap<String, JobHandle>(); private TaskPersistenceContext persistenceContext; public TaskDeadlinesServiceImpl() { } public TaskDeadlinesServiceImpl(TaskPersistenceContext persistenceContext) { this.persistenceContext = persistenceContext; } public void setPersistenceContext(TaskPersistenceContext persistenceContext) { this.persistenceContext = persistenceContext; } public void schedule(long taskId, long deadlineId, long delay, DeadlineType type) { Task task = persistenceContext.findTask(taskId); String deploymentId = task.getTaskData().getDeploymentId(); TimerService timerService = TimerServiceRegistry.getInstance().get(deploymentId + TimerServiceRegistry.TIMER_SERVICE_SUFFIX); if (timerService != null && timerService instanceof GlobalTimerService) { TaskDeadlineJob deadlineJob = new TaskDeadlineJob(taskId, deadlineId, type, deploymentId, task.getTaskData().getProcessInstanceId()); Trigger trigger = new IntervalTrigger( timerService.getCurrentTime(), null, null, -1, delay, 0, null, null ) ; JobHandle handle = timerService.scheduleJob(deadlineJob, new TaskDeadlineJobContext(deadlineJob.getId(), task.getTaskData().getProcessInstanceId()), trigger); logger.debug( "scheduling timer job for deadline {} and task {} using timer service {}", deadlineJob.getId(), taskId, timerService); jobHandles.put(deadlineJob.getId(), handle); } else { ScheduledFuture<ScheduledTaskDeadline> scheduled = scheduler.schedule(new ScheduledTaskDeadline(taskId, deadlineId, type, deploymentId, task.getTaskData().getProcessInstanceId()), delay, TimeUnit.MILLISECONDS); List<ScheduledFuture<ScheduledTaskDeadline>> knownFutures = null; if (type == DeadlineType.START) { knownFutures = startScheduledTaskDeadlines.get(taskId); } else if (type == DeadlineType.END) { knownFutures = endScheduledTaskDeadlines.get(taskId); } if (knownFutures == null) { knownFutures = new CopyOnWriteArrayList<ScheduledFuture<ScheduledTaskDeadline>>(); } knownFutures.add(scheduled); if (type == DeadlineType.START) { startScheduledTaskDeadlines.put(taskId, knownFutures); } else if (type == DeadlineType.END) { endScheduledTaskDeadlines.put(taskId, knownFutures); } } } public void unschedule(long taskId, DeadlineType type) { Task task = persistenceContext.findTask(taskId); String deploymentId = task.getTaskData().getDeploymentId(); Deadlines deadlines = ((InternalTask)task).getDeadlines(); TimerService timerService = TimerServiceRegistry.getInstance().get(deploymentId + TimerServiceRegistry.TIMER_SERVICE_SUFFIX); if (timerService != null && timerService instanceof GlobalTimerService) { if (type == DeadlineType.START) { List<Deadline> startDeadlines = deadlines.getStartDeadlines(); List<DeadlineSummary> resultList = (List<DeadlineSummary>)persistenceContext.queryWithParametersInTransaction("UnescalatedStartDeadlinesByTaskId", persistenceContext.addParametersToMap("taskId", taskId), ClassUtil.<List<DeadlineSummary>>castClass(List.class)); for (DeadlineSummary summary : resultList) { TaskDeadlineJob deadlineJob = new TaskDeadlineJob(summary.getTaskId(), summary.getDeadlineId(), DeadlineType.START, deploymentId, task.getTaskData().getProcessInstanceId()); logger.debug("unscheduling timer job for deadline {} {} and task {} using timer service {}", deadlineJob.getId(), summary.getDeadlineId(), taskId, timerService); JobHandle jobHandle = jobHandles.remove(deadlineJob.getId()); if (jobHandle == null) { jobHandle = ((GlobalTimerService) timerService).buildJobHandleForContext(new TaskDeadlineJobContext(deadlineJob.getId(), task.getTaskData().getProcessInstanceId())); } timerService.removeJob(jobHandle); // mark the deadlines so they won't be rescheduled again for (Deadline deadline : startDeadlines) { if (deadline.getId() == summary.getDeadlineId()) { deadline.setEscalated(true); } } } } else if (type == DeadlineType.END) { List<Deadline> endDeadlines = deadlines.getStartDeadlines(); List<DeadlineSummary> resultList = (List<DeadlineSummary>)persistenceContext.queryWithParametersInTransaction("UnescalatedEndDeadlinesByTaskId", persistenceContext.addParametersToMap("taskId", taskId), ClassUtil.<List<DeadlineSummary>>castClass(List.class)); for (DeadlineSummary summary : resultList) { TaskDeadlineJob deadlineJob = new TaskDeadlineJob(summary.getTaskId(), summary.getDeadlineId(), DeadlineType.END, deploymentId, task.getTaskData().getProcessInstanceId()); logger.debug("unscheduling timer job for deadline {} and task {} using timer service {}", deadlineJob.getId(), taskId, timerService); JobHandle jobHandle = jobHandles.remove(deadlineJob.getId()); if (jobHandle == null) { jobHandle = ((GlobalTimerService) timerService).buildJobHandleForContext(new TaskDeadlineJobContext(deadlineJob.getId(), task.getTaskData().getProcessInstanceId())); } timerService.removeJob(jobHandle); // mark the deadlines so they won't be rescheduled again for (Deadline deadline : endDeadlines) { if (deadline.getId() == summary.getDeadlineId()) { deadline.setEscalated(true); } } } } } else { List<ScheduledFuture<ScheduledTaskDeadline>> knownFutures = null; if (type == DeadlineType.START) { knownFutures = startScheduledTaskDeadlines.get(taskId); } else if (type == DeadlineType.END) { knownFutures = endScheduledTaskDeadlines.get(taskId); } if (knownFutures == null) { return; } Iterator<ScheduledFuture<ScheduledTaskDeadline>> it = knownFutures.iterator(); while (it.hasNext()) { ScheduledFuture<ScheduledTaskDeadline> scheduled = it.next(); try { if (!scheduled.isDone() && !scheduled.isCancelled()) { scheduled.cancel(true); } } catch (Exception e) { logger.error("Error while cancelling scheduled deadline task for Task with id {} -> {}", taskId, e); } } } } public void unschedule(long taskId, Deadline deadline, DeadlineType type) { Task task = persistenceContext.findTask(taskId); String deploymentId = task.getTaskData().getDeploymentId(); Deadlines deadlines = ((InternalTask)task).getDeadlines(); TimerService timerService = TimerServiceRegistry.getInstance().get(deploymentId + TimerServiceRegistry.TIMER_SERVICE_SUFFIX); if (timerService != null && timerService instanceof GlobalTimerService) { TaskDeadlineJob deadlineJob = new TaskDeadlineJob(taskId, deadline.getId(), type, deploymentId, task.getTaskData().getProcessInstanceId()); logger.debug("unscheduling timer job for deadline {} {} and task {} using timer service {}", deadlineJob.getId(), deadline.getId(), taskId, timerService); JobHandle jobHandle = jobHandles.remove(deadlineJob.getId()); if (jobHandle == null) { jobHandle = ((GlobalTimerService) timerService).buildJobHandleForContext(new TaskDeadlineJobContext(deadlineJob.getId(), task.getTaskData().getProcessInstanceId())); } timerService.removeJob(jobHandle); // mark the deadlines so they won't be rescheduled again deadline.setEscalated(true); } } public static class ScheduledTaskDeadline implements Callable<ScheduledTaskDeadline>, Serializable { private static final long serialVersionUID = 1L; private long taskId; private long deadlineId; private DeadlineType type; private String deploymentId; private Long processInstanceId; public ScheduledTaskDeadline(long taskId, long deadlineId, DeadlineType type, String deploymentId, Long processInstanceId) { this.taskId = taskId; this.deadlineId = deadlineId; this.type = type; this.deploymentId = deploymentId; this.processInstanceId = processInstanceId; } public long getTaskId() { return taskId; } public long getDeadlineId() { return deadlineId; } public DeadlineType getType() { return this.type; } public String getDeploymentId() { return deploymentId; } public long getProcessInstanceId() { return processInstanceId; } public ScheduledTaskDeadline call() throws Exception { RuntimeManager runtimeManager = null; RuntimeEngine engine = null; CommandExecutor executor = null; if (deploymentId != null && processInstanceId != null) { runtimeManager = RuntimeManagerRegistry.get().getManager(deploymentId); engine = runtimeManager.getRuntimeEngine(ProcessInstanceIdContext.get(processInstanceId)); executor = engine.getTaskService(); } else { executor = TaskDeadlinesServiceImpl.getInstance(); } try { executor.execute(new ExecuteDeadlinesCommand(taskId, deadlineId, type)); } catch (NullPointerException e) { logger.error("TaskDeadlineService instance is not available, most likely was not properly initialized - Job did not run!"); } finally { if (runtimeManager != null && engine != null) { runtimeManager.disposeRuntimeEngine(engine); } } return null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (deadlineId ^ (deadlineId >>> 32)); result = prime * result + (int) (taskId ^ (taskId >>> 32)); result = prime * result + type.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof ScheduledTaskDeadline)) { return false; } ScheduledTaskDeadline other = (ScheduledTaskDeadline) obj; if (deadlineId != other.deadlineId) { return false; } if (taskId != other.taskId) { return false; } if (type == null) { if (other.getType() != null) { return false; } } else if (type.equals(other.getType())) { return false; } return true; } } @SuppressWarnings("unused") private static class TaskDeadlineJob implements Job, Serializable { private static final long serialVersionUID = -2453658968872574615L; private long taskId; private long deadlineId; private DeadlineType type; private String deploymentId; private Long processInstanceId; public TaskDeadlineJob(long taskId, long deadlineId, DeadlineType type, String deploymentId, Long processInstanceId) { this.taskId = taskId; this.deadlineId = deadlineId; this.type = type; this.deploymentId = deploymentId; this.processInstanceId = processInstanceId; } public long getTaskId() { return taskId; } public long getDeadlineId() { return deadlineId; } public DeadlineType getType() { return this.type; } public String getDeploymentId() { return deploymentId; } public long getProcessInstanceId() { return processInstanceId; } @Override public void execute(JobContext ctx) { RuntimeManager runtimeManager = null; RuntimeEngine engine = null; CommandExecutor executor = null; if (deploymentId != null && processInstanceId != null) { runtimeManager = RuntimeManagerRegistry.get().getManager(deploymentId); engine = runtimeManager.getRuntimeEngine(ProcessInstanceIdContext.get(processInstanceId)); executor = engine.getTaskService(); } else { executor = TaskDeadlinesServiceImpl.getInstance(); } try { executor.execute(new ExecuteDeadlinesCommand(taskId, deadlineId, type)); } catch (NullPointerException e) { logger.error("TaskDeadlineService instance is not available, most likely was not properly initialized - Job did not run!"); } finally { if (runtimeManager != null && engine != null) { runtimeManager.disposeRuntimeEngine(engine); } } } public String getId() { return taskId +"_"+deadlineId+"_"+type; } } private static class TaskDeadlineJobContext implements NamedJobContext { private static final long serialVersionUID = -6838102884655249845L; private JobHandle jobHandle; private String jobName; private Long processInstanceId; public TaskDeadlineJobContext(String jobName, Long processInstanceId) { this.jobName = jobName; this.processInstanceId = processInstanceId; } @Override public void setJobHandle(JobHandle jobHandle) { this.jobHandle = jobHandle; } @Override public JobHandle getJobHandle() { return jobHandle; } @Override public String getJobName() { return jobName; } @Override public Long getProcessInstanceId() { return processInstanceId; } @Override public InternalWorkingMemory getWorkingMemory() { return null; } } public static CommandExecutor getInstance() { return instance; } public static synchronized void initialize(CommandExecutor instance) { if (instance != null) { TaskDeadlinesServiceImpl.instance = instance; getInstance().execute(new InitDeadlinesCommand()); } } public static synchronized void reset() { dispose(); scheduler = new ScheduledThreadPoolExecutor(3); } public static synchronized void dispose() { try { if (scheduler != null) { scheduler.shutdownNow(); } startScheduledTaskDeadlines.clear(); endScheduledTaskDeadlines.clear(); jobHandles.clear(); notificationListener = null; TaskDeadlinesServiceImpl.instance = null; } catch (Exception e) { logger.error("Error encountered when disposing TaskDeadlineService", e); } } }