/*
* 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.process.core.timer.impl;
import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.drools.core.time.InternalSchedulerService;
import org.drools.core.time.Job;
import org.drools.core.time.JobContext;
import org.drools.core.time.JobHandle;
import org.drools.core.time.SelfRemovalJobContext;
import org.drools.core.time.TimerService;
import org.drools.core.time.Trigger;
import org.drools.core.time.impl.TimerJobInstance;
import org.jbpm.process.core.timer.GlobalSchedulerService;
import org.jbpm.process.core.timer.NamedJobContext;
import org.jbpm.process.core.timer.SchedulerServiceInterceptor;
import org.jbpm.process.core.timer.impl.GlobalTimerService.GlobalJobHandle;
import org.jbpm.process.instance.timer.TimerManager.ProcessJobContext;
import org.jbpm.process.instance.timer.TimerManager.StartProcessJobContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ThreadPool based scheduler service backed by <code>ThreadPoolSchedulerService</code>
*
*/
public class ThreadPoolSchedulerService implements GlobalSchedulerService {
private static final Logger logger = LoggerFactory.getLogger(ThreadPoolSchedulerService.class);
private static final Integer FAILED_JOB_RETRIES = Integer.parseInt(System.getProperty("org.jbpm.timer.thread.retries", "5"));
private static final Integer FAILED_JOB_DELAY = Integer.parseInt(System.getProperty("org.jbpm.timer.thread.delay", "1000"));
private AtomicLong idCounter = new AtomicLong();
private ScheduledThreadPoolExecutor scheduler;
private TimerService globalTimerService;
private SchedulerServiceInterceptor interceptor = new DelegateSchedulerServiceInterceptor(this);
private int poolSize;
private ConcurrentHashMap<String, JobHandle> activeTimer = new ConcurrentHashMap<String, JobHandle>();
public ThreadPoolSchedulerService(int poolSize) {
this.poolSize = poolSize;
}
@Override
public void initScheduler(TimerService globalTimerService) {
this.globalTimerService = globalTimerService;
this.scheduler = new ScheduledThreadPoolExecutor(poolSize);
}
@Override
public void shutdown() {
try {
this.scheduler.shutdown();
if ( !this.scheduler.awaitTermination( 10, TimeUnit.SECONDS ) ) {
this.scheduler.shutdownNow();
}
} catch ( InterruptedException e ) {
this.scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
@Override
public JobHandle scheduleJob(Job job, JobContext ctx, Trigger trigger) {
Date date = trigger.hasNextFireTime();
if ( date != null ) {
String jobname = null;
if (ctx instanceof ProcessJobContext) {
ProcessJobContext processCtx = (ProcessJobContext) ctx;
jobname = processCtx.getSessionId() + "-" + processCtx.getProcessInstanceId() + "-" + processCtx.getTimer().getId();
if (processCtx instanceof StartProcessJobContext) {
jobname = "StartProcess-"+((StartProcessJobContext) processCtx).getProcessId()+ "-" + processCtx.getTimer().getId();
}
if (activeTimer.containsKey(jobname)) {
return activeTimer.get(jobname);
}
}
GlobalJDKJobHandle jobHandle = new GlobalJDKJobHandle( idCounter.getAndIncrement() );
TimerJobInstance jobInstance = globalTimerService.
getTimerJobFactoryManager().createTimerJobInstance( job,
ctx,
trigger,
jobHandle,
(InternalSchedulerService) globalTimerService );
jobHandle.setTimerJobInstance( (TimerJobInstance) jobInstance );
interceptor.internalSchedule( (TimerJobInstance) jobInstance );
if (jobname != null) {
activeTimer.put(jobname, jobHandle);
}
return jobHandle;
} else {
return null;
}
}
@Override
public boolean removeJob(JobHandle jobHandle) {
if (jobHandle == null) {
return false;
}
jobHandle.setCancel( true );
JobContext jobContext = ((GlobalJDKJobHandle) jobHandle).getTimerJobInstance().getJobContext();
try {
ProcessJobContext processCtx = null;
if (jobContext instanceof SelfRemovalJobContext) {
processCtx = (ProcessJobContext) ((SelfRemovalJobContext) jobContext).getJobContext();
} else {
processCtx = (ProcessJobContext) jobContext;
}
String jobname = processCtx.getSessionId() + "-" + processCtx.getProcessInstanceId() + "-" + processCtx.getTimer().getId();
if (processCtx instanceof StartProcessJobContext) {
jobname = "StartProcess-"+((StartProcessJobContext) processCtx).getProcessId()+ "-" + processCtx.getTimer().getId();
}
activeTimer.remove(jobname);
globalTimerService.getTimerJobFactoryManager().removeTimerJobInstance( ((GlobalJDKJobHandle) jobHandle).getTimerJobInstance() );
} catch (ClassCastException e) {
// do nothing in case ProcessJobContext was not given
}
boolean removed = this.scheduler.remove( (Runnable) ((GlobalJDKJobHandle) jobHandle).getFuture() );
return removed;
}
@Override
public void internalSchedule(TimerJobInstance timerJobInstance) {
if (scheduler.isShutdown()) {
return;
}
Date date = timerJobInstance.getTrigger().hasNextFireTime();
Callable<Void> item = (Callable<Void>) timerJobInstance;
GlobalJDKJobHandle jobHandle = (GlobalJDKJobHandle) timerJobInstance.getJobHandle();
long then = date.getTime();
long now = System.currentTimeMillis();
ScheduledFuture<Void> future = null;
if ( then >= now ) {
future = scheduler.schedule( new RetriggerCallable(scheduler, item),
then - now,
TimeUnit.MILLISECONDS );
} else {
future = scheduler.schedule( new RetriggerCallable(scheduler, item),
0,
TimeUnit.MILLISECONDS );
}
jobHandle.setFuture( future );
globalTimerService.getTimerJobFactoryManager().addTimerJobInstance( timerJobInstance );
}
public static class GlobalJDKJobHandle extends GlobalJobHandle implements Serializable {
private static final long serialVersionUID = 510l;
private transient ScheduledFuture<Void> future;
public GlobalJDKJobHandle(long id) {
super(id);
}
public ScheduledFuture<Void> getFuture() {
return future;
}
public void setFuture(ScheduledFuture<Void> future) {
this.future = future;
}
}
@Override
public JobHandle buildJobHandleForContext(NamedJobContext ctx) {
// this is in memory scheduler and the building of context is required for permanent ScueduleService only
return null;
}
@Override
public boolean isTransactional() {
return false;
}
@Override
public void setInterceptor(SchedulerServiceInterceptor interceptor) {
this.interceptor = interceptor;
}
@Override
public boolean retryEnabled() {
return false;
}
@Override
public boolean isValid(GlobalJobHandle jobHandle) {
return true;
}
private static class RetriggerCallable implements Callable<Void> {
private Callable<Void> delegate;
private ScheduledThreadPoolExecutor scheduler;
private int retries = 0;
RetriggerCallable(ScheduledThreadPoolExecutor scheduler, Callable<Void> delegate) {
this.scheduler = scheduler;
this.delegate = delegate;
}
@Override
public Void call() throws Exception {
try {
this.delegate.call();
return null;
} catch (Exception e) {
GlobalJDKJobHandle jobHandle = (GlobalJDKJobHandle) ((TimerJobInstance)this.delegate).getJobHandle();
if (retries < FAILED_JOB_RETRIES) {
ScheduledFuture<Void> future = this.scheduler.schedule( this,
FAILED_JOB_DELAY,
TimeUnit.MILLISECONDS );
jobHandle.setFuture( future );
retries++;
} else {
logger.error("Timer execution failed {} times in a roll, unscheduling ({})", FAILED_JOB_RETRIES, jobHandle);
}
throw e;
}
}
}
}