#SummitNow Using Scheduler with Long Running Activiti Workflow Tasks 6 November 2013 (Barcelona) 14 November 2013 (Boston) Bill Young, Flatirons Solutions
#SummitNow Overview Justification Problem Analysis Solution
#SummitNow Justification Manage Long Running (Batch-like) Processes in Workflow Queue Processes Limited Resources Throttle Running Jobs - Configuration
#SummitNow Sample Workflow
#SummitNow Problem Analysis Quartz Scheduler Available Activiti Doesn’t Provide Scheduling Activiti Does Provide Receive Tasks
#SummitNow Technologies Quartz Open Source Job Scheduling Library Already Integrated with Alfresco Java API
#SummitNow Technologies Activiti Open Source BPMN 2 Process Engine Heart of Alfresco Workflows (since 4.0)
#SummitNow Solution Split Tasks in Workflow Design Custom Scheduler Custom Jobs Custom Delegate Spring Configuration JDBC Job Store
#SummitNow Revised Workflow Diagram
#SummitNow Custom Delegate UML Diagram and Code
#SummitNow Custom Java Delegate
#SummitNow Abstract Java Delegate public abstract class AbstractCustomJavaDelegate extends BaseJavaDelegate { /** bean holding workflow settings */ private WorkflowServiceBean workflowSettings; /** the workflowSettings */ public WorkflowServiceBean getWorkflowSettings() { return workflowSettings; } /** value the workflowSettings to set */ public void setWorkflowSettings(final WorkflowServiceBean value) { this.workflowSettings = value; }
#SummitNow Abstract Scheduled Job Delegate public abstract class AbstractScheduledJobDelegate extends AbstractCustomJavaDelegate { /** Quartz Scheduler */ private Scheduler scheduler; /** Default Priority level */ private int defaultPriority; the scheduler */ public Scheduler getScheduler() { return scheduler; } value the scheduler to set */ public void setScheduler(final Scheduler value) { this.scheduler = value; } the defaultPriority */ public int getDefaultPriority() { return defaultPriority; } value the defaultPriority to set */ public void setDefaultPriority(final int value) { this.defaultPriority = value; }
#SummitNow Abstract Scheduled Job Delegate public Map getVariablesMap(final DelegateExecution execution) { Map variablesMap = new HashMap (); variablesMap.put(WORK_PACKAGE_ID_VAR, execution.getVariable(WORK_PACKAGE_ID_VAR)); variablesMap.put(WORKFLOW_DESC_VAR, execution.getVariable(WORKFLOW_DESC_VAR)); variablesMap.put(CONTENT_PACKAGE_VAR, execution.getVariable(CONTENT_PACKAGE_VAR)); variablesMap.put(WORKFLOW_DUE_DATE_VAR, execution.getVariable(WORKFLOW_DUE_DATE_VAR)); variablesMap.put(WORKFLOW_INSTANCE_ID, execution.getVariable(WORKFLOW_INSTANCE_ID)); variablesMap.put(TASK_OWNER, execution.getVariable(TASK_OWNER)); variablesMap.put(JOB_PRIORITY_VAR, Integer.valueOf(defaultPriority)); return variablesMap; }
#SummitNow Abstract Scheduled Job Delegate public void execute(final DelegateExecution execution) throws Exception { Map variablesMap = getVariablesMap(execution); String jobTraqId = (String) variablesMap.get(WORK_PACKAGE_ID_VAR); WorkflowInstance wkfInstance = getWorkflowSettings().getServiceRegistry().getWorkflowService().getWorkflowById( (String) variablesMap.get(WORKFLOW_INSTANCE_ID)); JobDetail jobDetail = new JobDetail(getJobPrefix() + jobTraqId, getJobsGroup(), getJobClass()); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put(JOB_TRAQ_ID, jobTraqId); jobDataMap.put(EXEC_VARIABLES, variablesMap); jobDataMap.put(WORKFLOW_INSTANCE, wkfInstance); jobDetail.setJobDataMap(jobDataMap); Trigger trigger = new SimpleTrigger(getTriggerPrefix() + jobTraqId, getTriggersGroup()); trigger.setPriority(((Integer) variablesMap.get(JOB_PRIORITY_VAR)).intValue()); getScheduler().scheduleJob(jobDetail, trigger); }
#SummitNow Concrete Scheduled Job Delegate public class IngestDelegate extends AbstractScheduledJobDelegate { public String getJobPrefix() { return INGESTION_JOB_PREFIX; } public String getJobsGroup() { return INGESTION_JOBS_GROUP; } public String getTriggerPrefix() { return INGESTION_TRIGGER_PREFIX; } public String getTriggersGroup() { return INGESTION_TRIGGERS_GROUP; } public Class getJobClass() { return ContentIngestionJob.class; } }
#SummitNow Bean Initialization
#SummitNow Workflow Settings Bean <bean id="CustomWorkflowSettings" class="com.flatirons.workflow.WorkflowServiceBean"> ${audit. alfresco.enabled}...
#SummitNow Java Delegate Beans <bean id="abstractJavaDelegate" parent="baseJavaDelegate" class="com.flatirons.workflow.AbstractCustomJavaDelegate" abstract="true"> <bean id="abstractScheduledJobDelegate" parent="abstractJavaDelegate" class="com.flatirons.workflow.AbstractScheduledJobDelegate“ abstract="true"> ${quartz.priority.default} <bean id="IngestDelegate" parent="abstractScheduledJobDelegate" class="com.flatirons.workflow.ingest.IngestDelegate">
#SummitNow Custom Scheduler UML diagram and code
#SummitNow Scheduler UML
#SummitNow Custom Std Scheduler public class FlatironsStdScheduler extends StdScheduler { /** bean holding workflow settings */ private WorkflowServiceBean workflowSettings; public FlatironsStdScheduler(final QuartzScheduler scheduler, final SchedulingContext schedulingContext) { super(scheduler, schedulingContext); } public WorkflowServiceBean getWorkflowSettings() { return workflowSettings; } public void setWorkflowSettings(final WorkflowServiceBean value) { workflowSettings = value; }
#SummitNow Scheduler Factory public class FlatironsSchedulerFactory extends StdSchedulerFactory { public FlatironsSchedulerFactory() { } protected Scheduler instantiate(final QuartzSchedulerResources resources, final QuartzScheduler scheduler) { SchedulingContext schedulingContext = new SchedulingContext(); schedulingContext.setInstanceId(resources.getInstanceId()); return new FlatironsStdScheduler(scheduler, schedulingContext); }
#SummitNow Scheduler Factory Bean public class FlatironsSchedulerFactoryBean extends SchedulerFactoryBean { private WorkflowServiceBean workflowSettings; public FlatironsSchedulerFactoryBean() { setSchedulerFactoryClass(FlatironsSchedulerFactory.class); } protected Scheduler createScheduler(final SchedulerFactory schedulerFactory, final String schedulerName) throws SchedulerException { FlatironsStdScheduler newScheduler = (FlatironsStdScheduler) super.createScheduler(schedulerFactory, schedulerName); newScheduler.setWorkflowSettings(this.workflowSettings); return newScheduler; }
#SummitNow Custom Jobs UML diagram and code
#SummitNow Jobs UML
#SummitNow Abstract Custom Job public abstract class FlatironsJob implements org.quartz.Job { public void execute(final JobExecutionContext context) throws JobExecutionException { try { JobDataMap jobDataMap = context.getMergedJobDataMap(); execute((WorkflowInstance) jobDataMap.get(WORKFLOW_INSTANCE), (FlatironsStdScheduler) context.getScheduler(), (Map ) jobDataMap.get(EXEC_VARIABLES)); } catch (Exception e) { throw new JobExecutionException(e); } finally { signalCompletion(null); } protected abstract void execute(WorkflowInstance workflowInstance, FlatironsStdScheduler scheduler, Map variablesMap) throws JobExecutionException; }
#SummitNow Concrete Custom Job public class ContentIngestionJob extends FlatironsJob { protected void execute(final WorkflowInstance workflowInstance, final FlatironsStdScheduler scheduler, final Map variablesMap) { ContentIngester contentIngester = new ContentIngester( scheduler.getWorkflowSettings(), variablesMap, workflowInstance); contentIngester.execute(); }
#SummitNow Spring Configuration <bean id="quartzIngestionSchedulerProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> 10 QRTZ_ING_ <bean id="ingestionScheduler" class="com.flatirons.workflow.scheduler.quartz.FlatironsSchedulerFactoryBean">
#SummitNow Quartz Properties org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.makeThreadsDaemons=false org.quartz.scheduler.makeSchedulerThreadDaemon=true org.quartz.scheduler.instanceId=AUTO org.quartz.scheduler.jmx.export=true org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.isClustered=false org.quartz.jobStore.clusterCheckinInterval=20000 org.quartz.jobStore.dataSource=myDS org.quartz.dataSource.myDS.driver=com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL=jdbc:mysql://localhost:3306/alfresco_quartz?useUnicode=yes&ch aracterEncoding=UTF-8 org.quartz.dataSource.myDS.user=quartz_usr org.quartz.dataSource.myDS.password=quartz...
#SummitNow JDBC Job Store create database if not exists alfresco_quartz; use alfresco_quartz; grant all on alfresco_quartz.* to identified by 'quartz'; grant all on alfresco_quartz.* to identified by 'quartz'; DROP TABLE IF EXISTS QRTZ_ING_JOB_DETAILS;... CREATE TABLE QRTZ_ING_JOB_DETAILS(... )... DROP TABLE IF EXISTS QRTZ_CON_JOB_DETAILS;... CREATE TABLE QRTZ_CON_JOB_DETAILS(... )...
#SummitNow Quartz Job Store mysql> show tables; | Tables_in_alfresco_quartz | |... | | QRTZ_CON_TRIGGERS | | QRTZ_CON_TRIGGER_LISTENERS | | QRTZ_ING_BLOB_TRIGGERS | | QRTZ_ING_CALENDARS | | QRTZ_ING_CRON_TRIGGERS | | QRTZ_ING_FIRED_TRIGGERS | | QRTZ_ING_JOB_DETAILS | | QRTZ_ING_JOB_LISTENERS | | QRTZ_ING_LOCKS | | QRTZ_ING_PAUSED_TRIGGER_GRPS | | QRTZ_ING_SCHEDULER_STATE | | QRTZ_ING_SIMPLE_TRIGGERS | | QRTZ_ING_TRIGGERS | | QRTZ_ING_TRIGGER_LISTENERS | |... |
#SummitNow Quartz Job Store mysql> desc QRTZ_ING_TRIGGERS; | Field | Type | Null | Key | Default | Extra | | TRIGGER_NAME | varchar(200) | NO | PRI | NULL | | | TRIGGER_GROUP | varchar(200) | NO | PRI | NULL | | | JOB_NAME | varchar(200) | NO | MUL | NULL | | | JOB_GROUP | varchar(200) | NO | | NULL | | | IS_VOLATILE | varchar(1) | NO | | NULL | | | DESCRIPTION | varchar(250) | YES | | NULL | | | NEXT_FIRE_TIME | bigint(13) | YES | | NULL | | | PREV_FIRE_TIME | bigint(13) | YES | | NULL | | | PRIORITY | int(11) | YES | | NULL | | | TRIGGER_STATE | varchar(16) | NO | | NULL | | | TRIGGER_TYPE | varchar(8) | NO | | NULL | | | START_TIME | bigint(13) | NO | | NULL | | | END_TIME | bigint(13) | YES | | NULL | | | CALENDAR_NAME | varchar(200) | YES | | NULL | | | MISFIRE_INSTR | smallint(2) | YES | | NULL | | | JOB_DATA | blob | YES | | NULL | | rows in set (0.02 sec)
#SummitNow Quartz Job Store mysql> select TRIGGER_NAME, JOB_NAME, PRIORITY, TRIGGER_STATE from QRTZ_ING_TRIGGERS; | TRIGGER_NAME | JOB_NAME | PRIORITY | TRIGGER_STATE | | IngestionTrigger_Summit E | IngestionJob_Summit E | 3 | COMPLETE | | IngestionTrigger_Summit F | IngestionJob_Summit F | 3 | WAITING | rows in set (0.00 sec)
#SummitNow Quartz Job Store mysql> desc QRTZ_ING_JOB_DETAILS; | Field | Type | Null | Key | Default | Extra | | JOB_NAME | varchar(200) | NO | PRI | NULL | | | JOB_GROUP | varchar(200) | NO | PRI | NULL | | | DESCRIPTION | varchar(250) | YES | | NULL | | | JOB_CLASS_NAME | varchar(250) | NO | | NULL | | | IS_DURABLE | varchar(1) | NO | | NULL | | | IS_VOLATILE | varchar(1) | NO | | NULL | | | IS_STATEFUL | varchar(1) | NO | | NULL | | | REQUESTS_RECOVERY | varchar(1) | NO | | NULL | | | JOB_DATA | blob | YES | | NULL | | rows in set (0.01 sec)
#SummitNow Quartz Job Store mysql> select JOB_NAME, JOB_CLASS_NAME from QRTZ_ING_JOB_DETAILS; | JOB_NAME | JOB_CLASS_NAME | | IngestionJob_Summit E | com.flatirons.workflow.scheduler.ContentIngestionJob | | IngestionJob_Summit F | com.flatirons.workflow.scheduler.ContentIngestionJob | rows in set (0.00 sec)
#SummitNow Additional Features Custom Page for Job Priority Clustering Remove Jobs from Queue
#SummitNow Contributors Sri Patil, Senior Developer, Flatirons
#SummitNow Questions
#SummitNow