001/** 002 * Copyright 2005-2017 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.kew.impl.stuck; 017 018import com.google.common.collect.Lists; 019import org.kuali.rice.kew.service.KEWServiceLocator; 020import org.quartz.DateBuilder; 021import org.quartz.Job; 022import org.quartz.JobBuilder; 023import org.quartz.JobDetail; 024import org.quartz.JobExecutionContext; 025import org.quartz.JobExecutionException; 026import org.quartz.SchedulerException; 027import org.quartz.SimpleScheduleBuilder; 028import org.quartz.Trigger; 029import org.quartz.TriggerBuilder; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import java.util.List; 034import java.util.UUID; 035import java.util.stream.Collectors; 036 037/** 038 * @author Eric Westfall 039 */ 040public class AutofixCollectorJob implements Job { 041 042 private static final Logger LOG = LoggerFactory.getLogger(AutofixCollectorJob.class); 043 private static final int PARTITION_SIZE = 50; 044 private static final String AUTOFIX_JOB_KEY_PREFIX = "Autofix Documents Job - "; 045 046 static final String AUTOFIX_QUIET_PERIOD_KEY = "autofixQuietPeriod"; 047 static final String AUTOFIX_MAX_ATTEMPTS_KEY = "autofixMaxAttempts"; 048 049 private volatile StuckDocumentService stuckDocumentService; 050 051 @Override 052 public void execute(JobExecutionContext context) throws JobExecutionException { 053 checkDependenciesAvailable(); 054 List<StuckDocumentIncident> newIncidents = 055 getStuckDocumentService().recordNewStuckDocumentIncidents(); 056 if (!newIncidents.isEmpty()) { 057 LOG.info("Identified " + newIncidents.size() + " new stuck documents"); 058 LOG.info("Scheduling jobs to attempt to fix the following documents: " 059 + newIncidents.stream().map(StuckDocumentIncident::getDocumentId).collect(Collectors.joining(", "))); 060 partitionAndScheduleAutofixJobs(newIncidents, context); 061 } 062 } 063 064 /** 065 * Checks if needed dependencies are available in order to run this job. Due to the fact that this is a quartz job, 066 * it could trigger while the system is offline and then immediately get fired when the system starts up and due to 067 * the startup process it could attempt to execute while not all of the necessary services are fully initialized. 068 */ 069 private void checkDependenciesAvailable() throws JobExecutionException { 070 if (getStuckDocumentService() == null) { 071 String message = "Dependencies are not available for the autofix collector job"; 072 LOG.warn(message); 073 throw new JobExecutionException(message); 074 } 075 } 076 077 private void partitionAndScheduleAutofixJobs(List<StuckDocumentIncident> incidents, JobExecutionContext context) { 078 Lists.partition(incidents, PARTITION_SIZE).forEach(incidentsPartition -> scheduleAutofixJobs(incidentsPartition, context)); 079 } 080 081 private void scheduleAutofixJobs(List<StuckDocumentIncident> incidents, JobExecutionContext context) { 082 List<String> incidentIds = incidents.stream().map(StuckDocumentIncident::getStuckDocumentIncidentId).collect(Collectors.toList()); 083 String jobKey = generateAutofixJobKey(); 084 int autofixQuietPeriod = autofixQuietPeriod(context); 085 int autofixMaxAttempts = autofixMaxAttempts(context); 086 JobDetail job = JobBuilder.newJob(AutofixDocumentsJob.class) 087 .withIdentity(jobKey) 088 .usingJobData(context.getMergedJobDataMap()) 089 .usingJobData(AutofixDocumentsJob.INCIDENT_IDS, String.join(",", incidentIds)) 090 .usingJobData(AutofixDocumentsJob.CURRENT_AUTOFIX_COUNT, 0) 091 .build(); 092 Trigger trigger = TriggerBuilder.newTrigger() 093 .forJob(job) 094 .startNow() 095 .withSchedule(SimpleScheduleBuilder.simpleSchedule() 096 .withIntervalInSeconds(autofixQuietPeriod) 097 .withRepeatCount(autofixMaxAttempts) 098 .withMisfireHandlingInstructionNextWithExistingCount()) 099 .startAt(DateBuilder.futureDate(autofixQuietPeriod, DateBuilder.IntervalUnit.SECOND)) 100 .build(); 101 try { 102 context.getScheduler().scheduleJob(job, trigger); 103 } catch (SchedulerException e) { 104 throw new IllegalStateException("Failed to schedule autofix job", e); 105 } 106 } 107 108 private String generateAutofixJobKey() { 109 return AUTOFIX_JOB_KEY_PREFIX + UUID.randomUUID().toString(); 110 } 111 112 private int autofixMaxAttempts(JobExecutionContext context) { 113 return context.getMergedJobDataMap().getInt(AUTOFIX_MAX_ATTEMPTS_KEY); 114 } 115 116 private int autofixQuietPeriod(JobExecutionContext context) { 117 return context.getMergedJobDataMap().getInt(AUTOFIX_QUIET_PERIOD_KEY); 118 } 119 120 protected StuckDocumentService getStuckDocumentService() { 121 if (this.stuckDocumentService == null) { 122 this.stuckDocumentService = KEWServiceLocator.getStuckDocumentService(); 123 } 124 return this.stuckDocumentService; 125 } 126 127 public void setStuckDocumentService(StuckDocumentService stuckDocumentService) { 128 this.stuckDocumentService = stuckDocumentService; 129 } 130 131}