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 org.kuali.rice.kew.api.KewApiServiceLocator; 019import org.kuali.rice.kew.doctype.bo.DocumentType; 020import org.kuali.rice.kew.service.KEWServiceLocator; 021import org.opensaml.xmlsec.signature.P; 022import org.quartz.DisallowConcurrentExecution; 023import org.quartz.Job; 024import org.quartz.JobExecutionContext; 025import org.quartz.JobExecutionException; 026import org.quartz.JobKey; 027import org.quartz.PersistJobDataAfterExecution; 028import org.quartz.SchedulerException; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import java.util.Arrays; 033import java.util.List; 034import java.util.stream.Collectors; 035 036@DisallowConcurrentExecution 037@PersistJobDataAfterExecution 038public class AutofixDocumentsJob implements Job { 039 040 private static final Logger LOG = LoggerFactory.getLogger(AutofixDocumentsJob.class); 041 042 static final String INCIDENT_IDS = "incidentIds"; 043 static final String CURRENT_AUTOFIX_COUNT = "currentAutofixCount"; 044 045 private volatile StuckDocumentService stuckDocumentService; 046 047 @Override 048 public void execute(JobExecutionContext context) throws JobExecutionException { 049 List<String> incidentIds = incidentIds(context); 050 checkDependenciesAvailable(incidentIds); 051 int currentAutofixCount = incrementAutofixCount(context); 052 List<StuckDocumentIncident> stillStuck = getStuckDocumentService().resolveIncidentsIfPossible(incidentIds); 053 if (stillStuck.isEmpty()) { 054 LOG.info("All stuck document incidents have been resolved, deleting autofix job."); 055 JobKey key = context.getJobDetail().getKey(); 056 try { 057 context.getScheduler().deleteJob(key); 058 } catch (SchedulerException e) { 059 throw new IllegalStateException("Failed to delete job with key: " + key, e); 060 } 061 } else if (currentAutofixCount > autofixMaxAttempts(context)) { 062 // at this point any remaining stuck docs are failures 063 LOG.info("Exceeded autofixMaxAttempts of " + autofixMaxAttempts(context) + ". Marking remaining " + stillStuck.size() + " stuck document incidents as failures."); 064 stillStuck.forEach(stuck -> getStuckDocumentService().recordIncidentFailure(stuck)); 065 } else { 066 // let's update the list of stuck doc incident ids and we know we have more iterations of this job scheduled so it should try again 067 LOG.info("There are " + stillStuck.size() + " stuck documents still remaining at autofix attempt " + currentAutofixCount + ", will try again."); 068 updateIncidentIds(stillStuck, context); 069 stillStuck.forEach(this::processIncident); 070 } 071 } 072 073 /** 074 * Checks if needed dependencies are available in order to run this job. Due to the fact that this is a quartz job, 075 * it could trigger while the system is offline and then immediately get fired when the system starts up and due to 076 * the startup process it could attempt to execute while not all of the necessary services are fully initialized. 077 */ 078 private void checkDependenciesAvailable(List<String> incidentIds) throws JobExecutionException { 079 if (getStuckDocumentService() == null) { 080 String message = "Dependencies are not available for this autofix documents job for stuck document incidents: " + incidentIds.toString(); 081 LOG.warn(message); 082 throw new JobExecutionException(message); 083 } 084 } 085 086 087 private int incrementAutofixCount(JobExecutionContext context) { 088 int currentAutofixCount = context.getJobDetail().getJobDataMap().getInt(CURRENT_AUTOFIX_COUNT); 089 currentAutofixCount++; 090 context.getJobDetail().getJobDataMap().put(CURRENT_AUTOFIX_COUNT, currentAutofixCount); 091 return currentAutofixCount; 092 } 093 094 private List<String> incidentIds(JobExecutionContext context) { 095 String incidentIdValues = context.getJobDetail().getJobDataMap().getString(INCIDENT_IDS); 096 String[] incidentIds = incidentIdValues.split(","); 097 return Arrays.asList(incidentIds); 098 } 099 100 private void updateIncidentIds(List<StuckDocumentIncident> incidents, JobExecutionContext context) { 101 List<String> incidentIds = incidents.stream().map(StuckDocumentIncident::getStuckDocumentIncidentId).collect(Collectors.toList()); 102 context.getJobDetail().getJobDataMap().put(INCIDENT_IDS, incidentIds.stream().collect(Collectors.joining(","))); 103 } 104 105 private void processIncident(StuckDocumentIncident incident) { 106 if (StuckDocumentIncident.Status.PENDING.equals(incident.getStatus())) { 107 incident = getStuckDocumentService().startFixingIncident(incident); 108 } 109 try { 110 tryToFix(incident); 111 } catch (Throwable t) { 112 // we catch and log here because we don't want one bad apple to ruin the whole bunch! 113 LOG.error("Error occurred when attmpting to fix stuck document incident for doc id " + incident.getDocumentId(), t); 114 } 115 } 116 117 private void tryToFix(StuckDocumentIncident incident) { 118 getStuckDocumentService().recordNewIncidentFixAttempt(incident); 119 String docId = incident.getDocumentId(); 120 DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByDocumentId(docId); 121 KewApiServiceLocator.getDocumentRequeuerService(documentType.getApplicationId(), docId, 0).refreshDocument(docId); 122 } 123 124 private int autofixMaxAttempts(JobExecutionContext context) { 125 return context.getMergedJobDataMap().getInt(AutofixCollectorJob.AUTOFIX_MAX_ATTEMPTS_KEY); 126 } 127 128 protected StuckDocumentService getStuckDocumentService() { 129 if (this.stuckDocumentService == null) { 130 this.stuckDocumentService = KEWServiceLocator.getStuckDocumentService(); 131 } 132 return this.stuckDocumentService; 133 } 134 135 public void setStuckDocumentService(StuckDocumentService stuckDocumentService) { 136 this.stuckDocumentService = stuckDocumentService; 137 } 138 139 140}