/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.sys.context;
import java.util.Arrays;
/**
* BatchStepTrigger writes .run files containing a job name and step name for BatchContainerStep to read.
* It loops and sleeps until either a .success or an .error file is written for the Step. Once a result file is found tt logs the results and exits.
*
* BatchStepTrigger also checks for BatchContainerStep's .runlock file. If it doesn't find one (indicating the batch container is not running) it exits.
* BatchStepTrigger adds a ConsoleAppender to its Logger if one hasn't been configured.
*
* Note that this class runs without starting the SpringContext. KFS Services and Beans are not available for use.
*
*/
public class BatchStepTrigger {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchStepTrigger.class);
private static BatchStepTriggerParameters batchStepTriggerParameters;
/**
* BatchStepTrigger is instantiated for each step in a brte script.
* The value used when exiting the system tells the brte script how to handle failures.
* 0: okay, 4: the step returned false, 8: an exception occurred in the execution of the step or the trigger
*
* - verify that the batch container is running, exit with an 8 if not
* - for each step
* - write a RUN semaphore file
* - wait and listen for a result file (either SUCCESS or ERROR)
* - if the result file is null the batch container is not running so remove the run file and exit with an 8
* - if the result file is an ERROR
* - if the file is EMPTY remove the result file and exit with a 4
* - otherwise log the error contained in the result file, remove the result file, and exit with an 8
* - otherwise remove the result file and exit with a 0
*
* @param args - refer to BatchStepTriggerParameters for details
*/
public static void main(String[] args) {
try {
Log4jConfigurer.configureLogging(false);
batchStepTriggerParameters = new BatchStepTriggerParameters(args);
String[] stepNames = getStepNames();
String jobName = getJobName();
int stepIndex = getStepIndex();
long sleepInterval = getSleepInterval();
BatchContainerDirectory batchContainerDirectory = getBatchContainerDirectory();
LOG.info("Executing Job: " + jobName + ", STEP"+ stepIndex +", Step(s): " + Arrays.toString(stepNames));
if (!batchContainerDirectory.isBatchContainerRunning()) {
//an instance of the batch container is not running - exit. Exit status: 8
LOG.error("The BatchContainer is not running - exiting without executing the steps: "+ Arrays.toString(stepNames));
LOG.info("Exit status: 8");
System.exit(8);
}
//Need to humanize 'i'; the index
for (int i = (stepIndex-1); i < stepNames.length; i++) {
String stepName = stepNames[i];
BatchStepFileDescriptor batchStepFile = new BatchStepFileDescriptor(jobName, stepName, BatchStepFileDescriptor.getFileExtensionRun());
//write step start file
batchContainerDirectory.writeBatchStepRunFile(batchStepFile, i);
//wait for a result file from BatchContainer
BatchStepFileDescriptor resultFile = listenForResultFile(batchContainerDirectory, batchStepFile, sleepInterval);
if (resultFile == null) {
//result file is null - something unexpected happened. Exit status: 8
batchContainerDirectory.removeBatchStepFileFromSystem(batchStepFile);
LOG.error("No result files were returned- exiting without knowing whether the Step was executed");
LOG.info("Exit status: 8");
System.exit(8);
}
if (resultFile.isStepFileAnErrorResultFile()) {
if (batchContainerDirectory.isFileEmpty(resultFile)) {
//do not execute any more steps, but job should succeed. Exit status: 4
LOG.error(batchStepFile +" failed");
batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);
LOG.info("Exit status: 4");
System.exit(4);
}
else {
//if file is not empty do not execute any more steps and fail job (write exception to log). Exit status: 8
LOG.error(batchStepFile +" failed with the following error message: ");
batchContainerDirectory.logFileContents(resultFile, LOG);
batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);
LOG.info("Exit status: 8");
System.exit(8);
}
}
batchContainerDirectory.removeBatchStepFileFromSystem(resultFile);
LOG.info("Exiting "+ batchStepFile);
}
//continue executing steps in the job. Exit status: 0
LOG.info("Exit status: 0");
System.exit(0);
}
catch (Throwable t) {
System.err.println("ERROR: Exception caught: ");
t.printStackTrace(System.err);
LOG.error(t);
System.exit(8);
}
}
/**
* Loop - look for a result file in the directory, if none is found then sleep.
* If the batch container is not running then write an error result file and return it.
*
* @param batchContainerDirectory the directory in which the semaphore files are located
* @param batchStepFile the step descriptor for the current step
* @param sleepInterval the amount of time to sleep while waiting for a result file
* @return the step descriptor of the result file
*/
private static BatchStepFileDescriptor listenForResultFile(BatchContainerDirectory batchContainerDirectory, BatchStepFileDescriptor batchStepFile, long sleepInterval) {
if (LOG.isDebugEnabled()) {
LOG.debug("Waiting for result file for "+ batchStepFile);
}
while (true) {
//look for a result file in file-system
BatchStepFileDescriptor resultFile = batchContainerDirectory.getResultFile(batchStepFile);
if (resultFile != null) {
LOG.info("Found result file: "+ resultFile.getName());
return resultFile;
}
if (batchContainerDirectory.isBatchContainerRunning()) {
sleep(sleepInterval);
}
else {
//the batch container is not running - return an error file with an exception
batchContainerDirectory.writeBatchStepErrorResultFile(batchStepFile, new RuntimeException("The BatchContainer is not running - exiting without knowing whether the Step executed"));
resultFile = batchContainerDirectory.getResultFile(batchStepFile);
return resultFile;
}
}
}
/**
* Sleep for the specified amount of time
*
* @param sleepInterval the amount of time (in milliseconds) to wait before looking for a result file
*/
private static void sleep(long sleepInterval) {
if (LOG.isDebugEnabled()) {
LOG.debug("Sleeping...");
}
try {
Thread.sleep(sleepInterval);
}
catch (InterruptedException e) {
throw new RuntimeException("BatchStepTrigger encountered interrupt exception while trying to wait for the specified batch step semaphore processing interval", e);
}
}
/**
* @return the names of the steps to be executed
*/
private static String[] getStepNames() {
return batchStepTriggerParameters.getStepNames();
}
/**
* @return the name of the job in which the steps are running
*/
private static String getJobName() {
return batchStepTriggerParameters.getJobName();
}
/**
* @return the index of the step in the job
*/
private static int getStepIndex() {
return batchStepTriggerParameters.getStepIndex();
}
/**
* @return the amount of time to sleep (in milliseconds) while waiting for a result file
*/
private static long getSleepInterval() {
return batchStepTriggerParameters.getSleepInterval();
}
/**
* @return the directory in which the semaphore files are located
*/
private static BatchContainerDirectory getBatchContainerDirectory() {
return batchStepTriggerParameters.getBatchContainerDirectory();
}
}