org.jbpm.process.instance.timer.TimerManager Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2017 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.process.instance.timer;
import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.drools.core.common.InternalKnowledgeRuntime;
import org.drools.core.common.InternalWorkingMemory;
import org.drools.core.marshalling.impl.MarshallerReaderContext;
import org.drools.core.marshalling.impl.MarshallerWriteContext;
import org.drools.serialization.protobuf.ProtobufInputMarshaller;
import org.drools.serialization.protobuf.ProtobufMessages;
import org.drools.serialization.protobuf.ProtobufMessages.Timers.Timer;
import org.drools.serialization.protobuf.ProtobufOutputMarshaller;
import org.drools.serialization.protobuf.TimersInputMarshaller;
import org.drools.serialization.protobuf.TimersOutputMarshaller;
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.CronTrigger;
import org.drools.core.time.impl.IntervalTrigger;
import org.jbpm.marshalling.impl.JBPMMessages;
import org.jbpm.marshalling.impl.ProtobufProcessMarshaller;
import org.jbpm.process.core.timer.impl.GlobalTimerService;
import org.jbpm.process.core.timer.impl.RegisteredTimerServiceDelegate;
import org.jbpm.process.instance.InternalProcessRuntime;
import org.jbpm.process.instance.ProcessInstance;
import org.jbpm.process.instance.ProcessRuntimeImpl;
import org.kie.api.runtime.KieSession;
import org.kie.api.time.SessionClock;
import org.jbpm.workflow.instance.NodeInstanceContainer;
import org.jbpm.workflow.instance.node.TimerNodeInstance;
import org.kie.internal.runtime.StatefulKnowledgeSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class TimerManager {
private static final Logger logger = LoggerFactory.getLogger(TimerManager.class);
private long timerId;
private InternalKnowledgeRuntime kruntime;
private TimerService timerService;
private Map timers = new ConcurrentHashMap();
public static final Job processJob = new ProcessJob();
public static final Job startProcessJob = new StartProcessJob();
public TimerManager(InternalKnowledgeRuntime kruntime, TimerService timerService) {
this.kruntime = kruntime;
this.timerService = timerService;
}
public void registerTimer(final TimerInstance timer, ProcessInstance processInstance) {
try {
kruntime.startOperation();
timer.setId(getTimerId(processInstance));
timer.setProcessInstanceId(processInstance.getId());
timer.setSessionId(((KieSession) kruntime).getIdentifier());
timer.setActivated(new Date());
Trigger trigger = null;
if (timer.getCronExpression() != null) {
Date startTime = new Date(timerService.getCurrentTime() + 1000);
trigger = new CronTrigger(timerService.getCurrentTime(), startTime, null, -1, timer.getCronExpression(), null, null);
// cron timers are by nature repeatable
timer.setPeriod(1);
} else {
trigger = new IntervalTrigger(timerService.getCurrentTime(), null, null, timer.getRepeatLimit(),
timer.getDelay(), timer.getPeriod(), null, null);
}
ProcessJobContext ctx = new ProcessJobContext(timer, trigger, processInstance.getId(), this.kruntime);
JobHandle jobHandle = this.timerService.scheduleJob(processJob, ctx, trigger);
timer.setJobHandle(jobHandle);
timers.put(timer.getId(), timer);
} finally {
kruntime.endOperation();
}
}
private long getTimerId(ProcessInstance processInstance) {
Object manager = kruntime.getEnvironment().get("RuntimeManager");
if (processInstance instanceof NodeInstanceContainer && manager != null && !manager.getClass().getSimpleName().equals("PerProcessInstanceRuntimeManager")) {
((NodeInstanceContainer)processInstance).getNodeInstances(true).stream().filter(TimerNodeInstance.class::isInstance).map(TimerNodeInstance.class::cast)
.map(TimerNodeInstance::getTimerId).max(Comparator.comparingLong(Long::longValue)).filter(l -> l > timerId).ifPresent(l -> timerId=l);
}
return ++timerId;
}
public void registerTimer(final TimerInstance timer, String processId, Map params) {
try {
kruntime.startOperation();
timer.setId(++timerId);
timer.setProcessInstanceId(-1l);
timer.setSessionId(((StatefulKnowledgeSession) kruntime).getIdentifier());
timer.setActivated(new Date());
Trigger trigger = null;
if (timer.getCronExpression() != null) {
Date startTime = new Date(timerService.getCurrentTime() + 1000);
trigger = new CronTrigger(timerService.getCurrentTime(), startTime, null, -1, timer.getCronExpression(), null, null);
// cron timers are by nature repeatable
timer.setPeriod(1);
} else {
trigger = new IntervalTrigger(timerService.getCurrentTime(), null, null, timer.getRepeatLimit(), timer.getDelay(),
timer.getPeriod(), null, null);
}
StartProcessJobContext ctx = new StartProcessJobContext(timer, trigger, processId, params, this.kruntime);
JobHandle jobHandle = this.timerService.scheduleJob(startProcessJob, ctx, trigger);
timer.setJobHandle(jobHandle);
timers.put(timer.getId(), timer);
} finally {
kruntime.endOperation();
}
}
public void internalAddTimer(final TimerInstance timer) {
long delay;
Date lastTriggered = timer.getLastTriggered();
if (lastTriggered == null) {
Date activated = timer.getActivated();
Date now = new Date();
long timespan = now.getTime() - activated.getTime();
delay = timer.getDelay() - timespan;
if (delay < 0) {
delay = 0;
}
} else {
Date now = new Date();
long timespan = now.getTime() - lastTriggered.getTime();
delay = timespan - timer.getPeriod();
if (delay < 0) {
delay = 0;
}
}
Trigger trigger = new IntervalTrigger(timerService.getCurrentTime(), null, null, -1, delay, timer.getPeriod(), null, null);
ProcessJobContext ctx = new ProcessJobContext(timer, trigger, timer.getProcessInstanceId(), this.kruntime);
JobHandle jobHandle = this.timerService.scheduleJob(processJob, ctx, trigger);
timer.setJobHandle(jobHandle);
timers.put(timer.getId(), timer);
}
public void cancelTimer(long processInstnaceId, long timerId) {
try {
kruntime.startOperation();
TimerInstance timer = timers.remove(timerId);
if (timer != null) {
timerService.removeJob(timer.getJobHandle());
} else if (timerService instanceof GlobalTimerService){
// second try if the cache here is not in sync
((GlobalTimerService) timerService).removeJobByTimerId(processInstnaceId, timerId);
} else if (timerService instanceof RegisteredTimerServiceDelegate) {
// second try if it is wrapped and the cache is not in sync
((GlobalTimerService) ((RegisteredTimerServiceDelegate) timerService).getTimerService()).removeJobByTimerId(processInstnaceId, timerId);
}
} finally {
kruntime.endOperation();
}
}
public void dispose() {
// for ( TimerInstance timer : timers.values() ) {
// timerService.removeJob( timer.getJobHandle() );
// }
if (timerService instanceof RegisteredTimerServiceDelegate) {
timers.clear();
return;
}
for (Iterator it = timers.values().iterator(); it.hasNext();) {
TimerInstance timer = it.next();
timerService.removeJob(timer.getJobHandle());
it.remove();
}
timerService.shutdown();
}
public TimerService getTimerService() {
return this.timerService;
}
public Collection getTimers() {
return timers.values();
}
public Map getTimerMap() {
return this.timers;
}
public long internalGetTimerId() {
return timerId;
}
public void internalSetTimerId(long timerId) {
this.timerId = timerId;
}
public void setTimerService(TimerService timerService) {
this.timerService = timerService;
}
public static class ProcessTimerOutputMarshaller implements TimersOutputMarshaller {
public Timer serialize(JobContext jobCtx, MarshallerWriteContext outputCtx) {
// do not store StartProcess timers as they are registered whenever session starts
if (jobCtx instanceof StartProcessJobContext) {
return null;
}
ProcessJobContext pctx = (ProcessJobContext) jobCtx;
return ProtobufMessages.Timers.Timer
.newBuilder()
.setType(ProtobufMessages.Timers.TimerType.PROCESS)
.setExtension(
JBPMMessages.procTimer,
JBPMMessages.ProcessTimer.newBuilder()
.setTimer(ProtobufProcessMarshaller.writeTimer(outputCtx, pctx.getTimer()))
.setTrigger(ProtobufOutputMarshaller.writeTrigger(pctx.getTrigger(), outputCtx)).build())
.build();
}
}
public static class ProcessTimerInputMarshaller implements TimersInputMarshaller {
private boolean disableInputMarshallerTimerRegistration = Boolean.getBoolean("org.jbpm.timer.disableUnmarshallerRegistration");
public void deserialize(MarshallerReaderContext inCtx, Timer timer) {
JBPMMessages.ProcessTimer ptimer = timer.getExtension(JBPMMessages.procTimer);
TimerService ts = inCtx.getWorkingMemory().getTimerService();
long processInstanceId = ptimer.getTimer().getProcessInstanceId();
Trigger trigger = ProtobufInputMarshaller.readTrigger(inCtx, ptimer.getTrigger());
TimerInstance timerInstance = ProtobufProcessMarshaller.readTimer(inCtx, ptimer.getTimer());
TimerManager tm = ((InternalProcessRuntime) inCtx.getWorkingMemory().getProcessRuntime()).getTimerManager();
// check if the timer instance is not already registered to avoid duplicated timers
if (!tm.getTimerMap().containsKey(timerInstance.getId()) && !disableInputMarshallerTimerRegistration) {
ProcessJobContext pctx = new ProcessJobContext(timerInstance, trigger, processInstanceId,
inCtx.getWorkingMemory().getKnowledgeRuntime(), false);
Date date = trigger.hasNextFireTime();
if (date != null) {
long then = date.getTime();
long now = pctx.getKnowledgeRuntime().getSessionClock().getCurrentTime();
// overdue timer
if (then < now) {
trigger = new OverdueTrigger(trigger);
}
}
trigger.initialize(pctx.getKnowledgeRuntime());
JobHandle jobHandle = ts.scheduleJob(processJob, pctx, trigger);
timerInstance.setJobHandle(jobHandle);
pctx.setJobHandle(jobHandle);
tm.getTimerMap().put(timerInstance.getId(), timerInstance);
}
}
}
public static class ProcessJob implements Job, Serializable {
private static final long serialVersionUID = 6004839244692770390L;
public void execute(JobContext c) {
ProcessJobContext ctx = (ProcessJobContext) c;
Long processInstanceId = ctx.getProcessInstanceId();
InternalKnowledgeRuntime kruntime = ctx.getKnowledgeRuntime();
try {
kruntime.startOperation();
if (processInstanceId == null) {
throw new IllegalArgumentException("Could not find process instance for timer ");
}
ctx.getTimer().setLastTriggered(
new Date(ctx.getKnowledgeRuntime(). getSessionClock().getCurrentTime()));
// if there is no more trigger reset period on timer so its node instance can be removed
if (ctx.getTrigger().hasNextFireTime() == null) {
ctx.getTimer().setPeriod(0);
}
((InternalProcessRuntime) kruntime.getProcessRuntime()).getSignalManager().signalEvent(processInstanceId,
"timerTriggered", ctx.getTimer());
TimerManager tm = ((InternalProcessRuntime) ctx.getKnowledgeRuntime().getProcessRuntime()).getTimerManager();
if (ctx.getTimer().getPeriod() == 0) {
tm.getTimerMap().remove(ctx.getTimer().getId());
tm.getTimerService().removeJob(ctx.getJobHandle());
}
} catch (Throwable e) {
logger.error("Error when executing timer job", e);
throw new RuntimeException(e);
} finally {
kruntime.endOperation();
}
}
}
public static class StartProcessJob implements Job, Serializable {
private static final long serialVersionUID = 1039445333595469160L;
public void execute(JobContext c) {
StartProcessJobContext ctx = (StartProcessJobContext) c;
InternalKnowledgeRuntime kruntime = ctx.getKnowledgeRuntime();
InternalProcessRuntime processRuntime = ((InternalProcessRuntime) ctx.getKnowledgeRuntime().getProcessRuntime());
TimerManager tm = processRuntime.getTimerManager();
if (!((ProcessRuntimeImpl) processRuntime).isActive()) {
logger.debug("Timer for starting process {} is ignored as the deployment is in deactivated state", ctx.getProcessId());
tm.getTimerMap().remove(ctx.getTimer().getId());
tm.getTimerService().removeJob(ctx.getJobHandle());
return;
}
try {
kruntime.startOperation();
ctx.getTimer().setLastTriggered(
new Date(ctx.getKnowledgeRuntime(). getSessionClock().getCurrentTime()));
// if there is no more trigger reset period on timer so its node instance can be removed
if (ctx.getTrigger().hasNextFireTime() == null) {
ctx.getTimer().setPeriod(0);
}
((ProcessRuntimeImpl)kruntime.getProcessRuntime()).startProcess(ctx.getProcessId(), ctx.getParamaeters(), "timer-" + ctx.getTimer().getName());
if (ctx.getTimer().getPeriod() == 0) {
tm.getTimerMap().remove(ctx.getTimer().getId());
tm.getTimerService().removeJob(ctx.getJobHandle());
}
} catch (Throwable e) {
logger.error("Error when executing start process " + ctx.getProcessId() + " timer job", e);
} finally {
kruntime.endOperation();
}
}
}
public static class ProcessJobContext implements JobContext {
private static final long serialVersionUID = 476843895176221627L;
private Long processInstanceId;
private transient InternalKnowledgeRuntime kruntime;
private TimerInstance timer;
private Trigger trigger;
private JobHandle jobHandle;
private Long sessionId;
private boolean isNew;
public ProcessJobContext(final TimerInstance timer, final Trigger trigger, final Long processInstanceId,
final InternalKnowledgeRuntime kruntime) {
this.timer = timer;
this.trigger = trigger;
this.processInstanceId = processInstanceId;
this.kruntime = kruntime;
this.sessionId = timer.getSessionId();
this.isNew = true;
}
public ProcessJobContext(final TimerInstance timer, final Trigger trigger, final Long processInstanceId,
final InternalKnowledgeRuntime kruntime, boolean isNew) {
this.timer = timer;
this.trigger = trigger;
this.processInstanceId = processInstanceId;
this.kruntime = kruntime;
this.sessionId = timer.getSessionId();
this.isNew = isNew;
}
public Long getProcessInstanceId() {
return processInstanceId;
}
public InternalKnowledgeRuntime getKnowledgeRuntime() {
return kruntime;
}
public Trigger getTrigger() {
return trigger;
}
public JobHandle getJobHandle() {
return this.jobHandle;
}
public void setJobHandle(JobHandle jobHandle) {
this.jobHandle = jobHandle;
}
public TimerInstance getTimer() {
return timer;
}
public Long getSessionId() {
return sessionId;
}
public void setKnowledgeRuntime(InternalKnowledgeRuntime kruntime) {
this.kruntime = kruntime;
}
@Override
public InternalWorkingMemory getWorkingMemory() {
return kruntime instanceof InternalWorkingMemory ? (InternalWorkingMemory)kruntime : null;
}
@Override
public boolean isNew() {
return isNew;
}
}
public static class StartProcessJobContext extends ProcessJobContext {
private static final long serialVersionUID = -5219141659893424294L;
private String processId;
private Map paramaeters;
public StartProcessJobContext(TimerInstance timer, Trigger trigger, String processId, Map params,
InternalKnowledgeRuntime kruntime) {
super(timer, trigger, null, kruntime);
this.processId = processId;
this.paramaeters = params;
}
public String getProcessId() {
return processId;
}
public void setProcessId(String processId) {
this.processId = processId;
}
public Map getParamaeters() {
return paramaeters;
}
public void setParamaeters(Map paramaeters) {
this.paramaeters = paramaeters;
}
@Override
public boolean isNew() {
return false;
}
}
/**
* Overdue aware trigger that introduces fixed delay to allow completion of session initialization
*
*/
public static class OverdueTrigger implements Trigger {
private static final long serialVersionUID = -2368476147776308013L;
public static final long OVERDUE_DELAY = Long.parseLong(System.getProperty("jbpm.overdue.timer.delay", "2000"));
private Trigger orig;
private transient InternalKnowledgeRuntime kruntime;
public OverdueTrigger(Trigger orig) {
this.orig = orig;
}
@Override
public void initialize(InternalKnowledgeRuntime kruntime) {
this.kruntime = kruntime;
}
public Date hasNextFireTime() {
Date date = orig.hasNextFireTime();
if (date == null) {
return null;
}
long then = date.getTime();
if(kruntime == null) {
// it means there was an error about session so kruntime could not be calculated
// we need to return null in this case to repeat the same trigger.
return null;
}
long now = kruntime.getSessionClock().getCurrentTime();
// overdue timer
if (then < now) {
return new Date((now + OVERDUE_DELAY));
} else {
return orig.hasNextFireTime();
}
}
public Date nextFireTime() {
return orig.nextFireTime();
}
}
}