/*
* 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.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.batch.Job;
import org.kuali.kfs.sys.batch.Step;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.service.KualiModuleService;
import org.kuali.rice.krad.service.ModuleService;
/**
* BatchStepExecutor executes a Step in its own Thread and writes either a .success or .error file after execution.
* This class notifies the ContainerStepListener when a Step has started and when it has completed.
*
* BatchStepExecutor adds a ConsoleAppender to its Logger if one hasn't been configured.
*
*/
public class BatchStepExecutor implements Runnable {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchStepExecutor.class);
private ParameterService parameterService;
private DateTimeService dateTimeService;
private BatchContainerDirectory batchContainerDirectory;
private BatchStepFileDescriptor batchStepFile;
private Step step;
private int stepIndex;
private Appender ndcAppender;
private boolean ndcSet;
private String logFileName;
private List<ContainerStepListener> containerStepListeners;
/**
* @param parameterService the ParameterService used by Job
* @param dateTimeService the DateTimeService used by Job
* @param batchContainerDirectory the batch container directory
* @param batchStepFile the descriptor containing information about the step to execute
* @param step the Step to execute
* @param stepIndex the index of the step in the job
*/
public BatchStepExecutor(ParameterService parameterService, DateTimeService dateTimeService, BatchContainerDirectory batchContainerDirectory, BatchStepFileDescriptor batchStepFile, Step step, int stepIndex) {
this.parameterService = parameterService;
this.dateTimeService = dateTimeService;
this.batchContainerDirectory = batchContainerDirectory;
this.batchStepFile = batchStepFile;
this.step = step;
this.stepIndex = stepIndex;
this.containerStepListeners = new ArrayList<ContainerStepListener>();
LOG.info("Initialized thread executor for "+ batchStepFile);
}
/**
* Execute the Step via Job.runStep(). Setup NDC logging so the Step has its own log file. Remove the NDC logging once the step is finished executing.
* Notify the ContainerStepListeners when the step starts and finishes.
*/
@Override
public void run() {
Date stepRunDate = dateTimeService.getCurrentDate();
batchStepFile.setStartedDate(stepRunDate);
batchStepFile.setStepIndex(new Integer(stepIndex));
setupNDCLogging();
notifyStepStarted();
try {
LOG.info("Running "+ batchStepFile);
boolean result = Job.runStep(parameterService, batchStepFile.getJobName(), stepIndex, step, stepRunDate);
if (result) {
LOG.info("Step returned true");
batchContainerDirectory.writeBatchStepSuccessfulResultFile(batchStepFile);
}
else {
LOG.info("Step returned false");
batchContainerDirectory.writeBatchStepErrorResultFile(batchStepFile);
}
} catch (Exception throwable) {
//LOG.info("Step threw an error: ", throwable);
LOG.warn(batchStepFile + " threw " + throwable.getClass().getName() + ". Look at the step log to see the details. throwable.getMessage(): " + throwable.getMessage());
batchContainerDirectory.writeBatchStepErrorResultFile(batchStepFile, throwable);
} finally {
notifyStepFinished();
resetNDCLogging();
}
}
/**
* Adds a ContainerStepListener for step start and completion notifications
*
* @param listener the ContainerStepListener
*/
public void addContainerStepListener(ContainerStepListener listener) {
this.containerStepListeners.add(listener);
}
/**
* Add a new appender and context to the NDC for this execution of the step
*/
private void setupNDCLogging() {
String nestedDiagnosticContext = getNestedDiagnosticContext();
logFileName = getLogFileName(nestedDiagnosticContext);
ndcAppender = null;
ndcSet = false;
try {
ndcAppender = new FileAppender(KFSConstants.BATCH_LOGGER_DEFAULT_PATTERN_LAYOUT, logFileName);
ndcAppender.addFilter(new NDCFilter(nestedDiagnosticContext));
Logger.getRootLogger().addAppender(ndcAppender);
NDC.push(nestedDiagnosticContext);
ndcSet = true;
} catch (Exception ex) {
LOG.warn("Could not initialize custom logging for step: " + step.getName(), ex);
}
}
/**
* Remove the appender and context from the NDC
*/
private void resetNDCLogging() {
if ( ndcSet ) {
ndcAppender.close();
Logger.getRootLogger().removeAppender(ndcAppender);
NDC.pop();
}
}
/**
* Constructs the name of the log file to write to for this execution of the step
*
* @param nestedDiagnosticContext the context returned by getNestedDiagnosticContext() for this step
* @return the name of the log file
*/
private String getLogFileName(String nestedDiagnosticContext) {
return SpringContext.getBean( ConfigurationService.class ).getPropertyValueAsString(KFSConstants.REPORTS_DIRECTORY_KEY)
+ File.separator
+ nestedDiagnosticContext + ".log";
}
/**
* @return the nested diagnostic context string for this step's log file
*/
@SuppressWarnings("unchecked")
private String getNestedDiagnosticContext() {
Step unProxiedStep = (Step) ProxyUtils.getTargetIfProxied(step);
Class stepClass = unProxiedStep.getClass();
ModuleService module = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService( stepClass );
String nestedDiagnosticContext =
StringUtils.substringAfter( module.getModuleConfiguration().getNamespaceCode(), "-").toLowerCase()
+ File.separator + step.getName()
+ "-" + dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate());
return nestedDiagnosticContext;
}
/**
* Notify the ContainerStepListeners that the Step has started
*/
private void notifyStepStarted() {
String shortLogFileName = getShortLogFileName();
for(ContainerStepListener listener : this.containerStepListeners) {
listener.stepStarted(batchStepFile, shortLogFileName);
}
}
/**
* Notify the ContainerStepListeners that the Step has completed
*/
private void notifyStepFinished() {
BatchStepFileDescriptor resultFile = batchContainerDirectory.getResultFile(batchStepFile);
resultFile.setCompletedDate(dateTimeService.getCurrentDate());
resultFile.setStepIndex(new Integer(stepIndex));
String shortLogFileName = getShortLogFileName();
for(ContainerStepListener listener : this.containerStepListeners) {
listener.stepFinished(resultFile, shortLogFileName);
}
}
/**
* Returns just the name of the log file without the absolute path
*
* @return just the name of the log file (not the entire path)
*/
private String getShortLogFileName() {
String shortLogFileName = logFileName;
File logFile = new File(logFileName);
if (logFile.exists()) {
shortLogFileName = logFile.getName();
}
return shortLogFileName;
}
}