/*
* Copyright 2012 JBoss Inc
*
* 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.jboss.processFlow.knowledgeService;
import java.util.Collection;
import java.util.Date;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.apache.commons.lang.StringUtils;
import org.drools.common.InternalWorkingMemory;
import org.drools.common.Scheduler.ActivationTimerJobContext;
import org.drools.impl.StatefulKnowledgeSessionImpl;
import org.drools.time.AcceptsTimerJobFactoryManager;
import org.drools.time.InternalSchedulerService;
import org.drools.time.Job;
import org.drools.time.JobContext;
import org.drools.time.JobHandle;
import org.drools.time.TimerService;
import org.drools.time.Trigger;
import org.drools.time.impl.CronExpression;
import org.drools.time.impl.CronTrigger;
import org.drools.time.impl.DefaultJobHandle;
import org.drools.time.impl.DefaultTimerJobFactoryManager;
import org.drools.time.impl.IntervalTrigger;
import org.drools.time.impl.TimerJobFactoryManager;
import org.drools.time.impl.TimerJobInstance;
import org.drools.time.SessionClock;
import org.jbpm.process.instance.timer.TimerManager.ProcessJobContext;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jboss.processFlow.knowledgeService.IBaseKnowledgeSession;
import org.jboss.processFlow.util.GlobalQuartzJobHandle;
/**
* Quartz based <code>GlobalSchedulerService</code> that is configured according
* to Quartz rules and allows to store jobs in data base. With that it survives
* server crashes and operates as soon as service is initialized without session
* being active.
*
*/
public class QuartzSchedulerService implements TimerService, InternalSchedulerService, AcceptsTimerJobFactoryManager, SessionClock {
public static final String TIMER_JOB_HANDLE = "timerJobHandle";
public static final String JOB_GROUP = "jbpm";
public static final String PROCESS_JOB = "ProcessJob";
public static final String ACTIVATION_TIMER_JOB = "ActivationTimerJob";
public static final String RULE_NAME_START_PREFIX = "RuleFlow-Start-";
private static final Logger log = LoggerFactory.getLogger(QuartzSchedulerService.class);
// Quartz Scheduler
private static Scheduler scheduler;
// For purposes of PER_PROCESS_INSTANCE architecture, will never populate the drools TimerJobFactoryManager
// subsequently, the drools session will no longer include the job trigger
// the job trigger will always be managed by this implementation's Quartz scheduler
private static TimerJobFactoryManager jobFactoryManager = DefaultTimerJobFactoryManager.instance;
// used to signal the process instance
private static IBaseKnowledgeSession kSessionProxy;
public static void start() throws Exception{
Context jndiContext;
jndiContext = new InitialContext();
kSessionProxy = (IBaseKnowledgeSession)jndiContext.lookup(IBaseKnowledgeSession.BASE_JNDI);
scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
}
public static void stop() throws Exception {
scheduler.shutdown();
scheduler = null;
}
public JobHandle scheduleJob(Job job, JobContext ctx, Trigger droolsTrig) {
GlobalQuartzJobHandle quartzJobHandle= null;
if (ctx instanceof ProcessJobContext) {
// seems to be used with timers using drools timer expression
ProcessJobContext processCtx = (ProcessJobContext) ctx;
StatefulKnowledgeSessionImpl wM = (StatefulKnowledgeSessionImpl)processCtx.getKnowledgeRuntime();
String jobname = PROCESS_JOB +"-"+processCtx.getProcessInstanceId() + "-" + processCtx.getTimer().getId();
quartzJobHandle = new GlobalQuartzJobHandle(jobname, JOB_GROUP, wM.getId());
} else if (ctx instanceof ActivationTimerJobContext) {
// seems to be used with timers using cron expression
InternalWorkingMemory wM = (InternalWorkingMemory)((ActivationTimerJobContext)ctx).getAgenda().getWorkingMemory();
//String processId = ((ActivationTimerJobContext) ctx).getScheduledAgendaItem().
String ruleName = ((ActivationTimerJobContext)ctx).getScheduledAgendaItem().getRule().getName();
String processId = StringUtils.substringAfter(ruleName, RULE_NAME_START_PREFIX);
String jobname = ACTIVATION_TIMER_JOB +"-"+processId+"-"+wM.getId();
quartzJobHandle = new GlobalQuartzJobHandle(jobname, JOB_GROUP, wM.getId());
} else {
throw new RuntimeException("scheduleJob() unknown jobContext = "+ctx);
}
try {
org.quartz.Trigger triggerObj = configureJobHandleAndQuartzTrigger(quartzJobHandle, droolsTrig);
// Define a quartz job detail instance and add jobHandle to its Map
JobDetail jdetail = new JobDetail(quartzJobHandle.getJobName(), quartzJobHandle.getJobGroup(), QuartzJob.class);
jdetail.getJobDataMap().put(TIMER_JOB_HANDLE, quartzJobHandle);
if (null == scheduler.getJobDetail(quartzJobHandle.getJobName(), quartzJobHandle.getJobGroup())) {
scheduler.scheduleJob(jdetail, triggerObj);
} else {
// need to add the job again to replace existing especially important if jobs are persisted in db
log.warn("scheduleJob() uh-oh!!!! this job was already scheduled : "+quartzJobHandle.getJobName());
scheduler.addJob(jdetail, true);
triggerObj.setJobName(quartzJobHandle.getJobName());
triggerObj.setJobGroup(quartzJobHandle.getJobGroup());
scheduler.rescheduleJob(quartzJobHandle.getJobName()+"_trigger", quartzJobHandle.getJobGroup(), triggerObj);
}
// drools legacy: triggers are not to be stored in drools sessions
//TimerJobInstance jobInstance = this.jobFactoryManager.createTimerJobInstance( job, ctx, trigger, quartzJobHandle, this);
//this.jobFactoryManager.addTimerJobInstance( timerJobInstance );
// drools legacy: return a DefaultJobHandle
return new DefaultJobHandle(0L);
} catch (Exception x) {
throw new RuntimeException(x);
}
}
private static org.quartz.Trigger configureJobHandleAndQuartzTrigger(GlobalQuartzJobHandle jHandle, org.drools.time.Trigger droolsTrig) throws Exception{
org.quartz.Trigger quartzTrig = null;
String jName = jHandle.getJobName()+"_trigger";
String jGroup = jHandle.getJobGroup();
if(droolsTrig instanceof IntervalTrigger){
IntervalTrigger iTrig = (IntervalTrigger)droolsTrig;
Date firstFire = iTrig.hasNextFireTime();
// drools repeatLimit = quartz repeatCount
int repeatCount = iTrig.getRepeatLimit();
long interval = iTrig.getPeriod();
if(interval <= 0)
repeatCount = 0;
quartzTrig = new SimpleTrigger(jName, jGroup, firstFire, iTrig.getEndTime(), repeatCount, interval);
jHandle.setInterval(interval);
jHandle.setTimerExpression(iTrig.toString());
}else if(droolsTrig instanceof CronTrigger){
CronTrigger cTrigger = (CronTrigger)droolsTrig;
CronExpression cExpression = cTrigger.getCronEx();
//Date onlyFire = cTrigger.getNextFireTime();
//quartzTrig = new SimpleTrigger(jName, jGroup, onlyFire); // fires one time and does not repeat
quartzTrig = new org.quartz.CronTrigger(jName, jGroup, cExpression.getCronExpression());
}else {
throw new RuntimeException("configureJobHandleAndQuartzTrigger() need to implement appropriate handling of the following type of trigger : "+droolsTrig.getClass().toString());
}
return quartzTrig;
}
public static boolean deleteJob(GlobalQuartzJobHandle jobHandle) {
try {
return scheduler.deleteJob(jobHandle.getJobName(), jobHandle.getJobGroup());
} catch (Exception x) {
throw new RuntimeException(x);
}
}
public static String getCurrentTimerJobsAsJson(String jobGroup) throws SchedulerException{
StringBuilder sBuilder = new StringBuilder("{\n\t\"jobs\":[\n\t");
String[] jobNames = scheduler.getJobNames(jobGroup);
if(jobNames.length > 0){
int x = 0;
for(String name : jobNames){
if(x>0)
sBuilder.append(",\n\t");
// show the initial jobDetail for now. could expand by also listing current triggers
JobDetail jDetail = scheduler.getJobDetail(name, jobGroup);
GlobalQuartzJobHandle jHandle = (GlobalQuartzJobHandle) jDetail.getJobDataMap().get(TIMER_JOB_HANDLE);
sBuilder.append("\t{\"jName\":\"");
sBuilder.append(name);
sBuilder.append("\":\"timer\":\"");
sBuilder.append(jHandle.getTimerExpression());
sBuilder.append("\"");
x++;
}
}
sBuilder.append("\n\t]\n}");
return sBuilder.toString();
}
public static int purgeCurrentTimerJobs(String jobGroup) throws SchedulerException{
String[] jobNames = scheduler.getJobNames(jobGroup);
for(String name : jobNames){
scheduler.deleteJob(name, jobGroup);
}
return jobNames.length;
}
public static class QuartzJob implements org.quartz.Job {
public QuartzJob() {}
public void execute(JobExecutionContext qContext) throws JobExecutionException {
GlobalQuartzJobHandle jHandle = (GlobalQuartzJobHandle)(qContext.getMergedJobDataMap().get(QuartzSchedulerService.TIMER_JOB_HANDLE));
if(qContext.getNextFireTime() == null)
jHandle.setInterval(0L);
int pState = kSessionProxy.processJobExecutionContext(qContext);
/* could delete job in quartz scheduler; but does not seem to be a use case where this is required.
* quartz will flush its scheduler of a job once that job no longer has a nextFireTime
if(ProcessInstance.STATE_COMPLETED == pState || jHandle.getInterval() == 0){
boolean dSuccess = deleteJob(jHandle);
log.info("execute() just signaled. pState = "+pState+" : jobName = "+jHandle.getJobName()+" : successful deletion = "+dSuccess);
}else {
log.info("execute() just signaled. pState = "+pState+" : jobName = "+jHandle.getJobName());
}
*/
}
}
/* *********************** LEGACY FUNCTIONS *******************************/
public boolean removeJob(JobHandle jobHandle) {
/* NOTE: BRMS5.3.1, as part of disposing of a session, this function will be invoked.
Subsequently, timer will never fire.
Will move this functionality to a 'deleteJob' function that is invoked when needed
*/
return true;
}
public void shutdown() {
//log.warn("shutdown() function not valid");
}
public void internalSchedule(TimerJobInstance timerJobInstance) {
// not needed, all scheduling functionality is handled in: scheduleJob(...)
}
public void forceShutdown() {
//log.warn("forceShutdown() function not valid");
}
public synchronized void initScheduler() {
// will handle this in start() function
}
/*
public JobHandle buildJobHandleForContext(NamedJobContext ctx) {
return null;
}
*/
@Override
public long getCurrentTime() {
return System.currentTimeMillis();
}
@Override
public long getTimeToNextJob() {
return 0;
}
@Override
public Collection<TimerJobInstance> getTimerJobInstances() {
return jobFactoryManager.getTimerJobInstances();
}
@Override
public TimerJobFactoryManager getTimerJobFactoryManager() {
return this.jobFactoryManager;
}
@Override
public void setTimerJobFactoryManager(TimerJobFactoryManager arg0) {
this.jobFactoryManager= arg0;
}
}