/*
* 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.batch;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Calendar;
import org.apache.commons.lang.StringUtils;
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.KFSKeyConstants;
import org.kuali.kfs.sys.batch.service.SchedulerService;
import org.kuali.kfs.sys.context.NDCFilter;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.krad.service.MailService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class JobListener implements org.quartz.JobListener {
private static final Logger LOG = Logger.getLogger(JobListener.class);
protected static final String NAME = "jobListener";
public static final String REQUESTOR_EMAIL_ADDRESS_KEY = "requestorEmailAdress";
protected SchedulerService schedulerService;
protected ConfigurationService configurationService;
protected MailService mailService;
protected DateTimeService dateTimeService;
/**
* @see org.quartz.JobListener#jobWasExecuted(org.quartz.JobExecutionContext, org.quartz.JobExecutionException)
*/
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException jobExecutionException) {
if (jobExecutionContext.getJobInstance() instanceof Job) {
try {
if (!((Job) jobExecutionContext.getJobInstance()).isNotRunnable()) {
notify(jobExecutionContext, schedulerService.getStatus(jobExecutionContext.getJobDetail()));
}
} finally {
completeLogging(jobExecutionContext);
}
}
}
/**
* @see org.quartz.JobListener#jobToBeExecuted(org.quartz.JobExecutionContext)
*/
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
if (jobExecutionContext.getJobInstance() instanceof Job) {
schedulerService.initializeJob(jobExecutionContext.getJobDetail().getName(), (Job) jobExecutionContext.getJobInstance());
initializeLogging(jobExecutionContext);
// We only want to auto-cancel executions if they are part of a master scheduling job
// Otherwise, this is a standalone job and should fire, regardless of prior status
if ( jobExecutionContext.getMergedJobDataMap().containsKey(Job.MASTER_JOB_NAME) ) {
if (schedulerService.shouldNotRun(jobExecutionContext.getJobDetail())) {
((Job) jobExecutionContext.getJobInstance()).setNotRunnable(true);
}
} else {
((Job) jobExecutionContext.getJobInstance()).setNotRunnable(false);
}
}
}
/**
* @see org.quartz.JobListener#jobExecutionVetoed(org.quartz.JobExecutionContext)
*/
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
if (jobExecutionContext.getJobInstance() instanceof Job) {
throw new UnsupportedOperationException("JobListener does not implement jobExecutionVetoed(JobExecutionContext jobExecutionContext)");
}
}
protected void initializeLogging(JobExecutionContext jobExecutionContext) {
try {
Calendar startTimeCalendar = dateTimeService.getCurrentCalendar();
StringBuilder nestedDiagnosticContext = new StringBuilder(StringUtils.substringAfter(BatchSpringContext.getJobDescriptor(jobExecutionContext.getJobDetail().getName()).getNamespaceCode(), "-").toLowerCase()).append(File.separator).append(jobExecutionContext.getJobDetail().getName()).append("-").append(dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate()));
((Job) jobExecutionContext.getJobInstance()).setNdcAppender(new FileAppender(Logger.getRootLogger().getAppender("StdOut").getLayout(), getLogFileName(nestedDiagnosticContext.toString())));
((Job) jobExecutionContext.getJobInstance()).getNdcAppender().addFilter(new NDCFilter(nestedDiagnosticContext.toString()));
Logger.getRootLogger().addAppender(((Job) jobExecutionContext.getJobInstance()).getNdcAppender());
NDC.push(nestedDiagnosticContext.toString());
}
catch (IOException e) {
LOG.warn("Could not initialize special custom logging for job: " + jobExecutionContext.getJobDetail().getName(), e);
}
}
private void completeLogging(JobExecutionContext jobExecutionContext) {
((Job) jobExecutionContext.getJobInstance()).getNdcAppender().close();
Logger.getRootLogger().removeAppender(((Job) jobExecutionContext.getJobInstance()).getNdcAppender());
NDC.pop();
}
protected String getLogFileName(String nestedDiagnosticContext) {
return new StringBuilder(configurationService.getPropertyValueAsString(KFSConstants.REPORTS_DIRECTORY_KEY)).append(File.separator).append(nestedDiagnosticContext.toString()).append(".log").toString();
}
protected void notify(JobExecutionContext jobExecutionContext, String jobStatus) {
try {
StringBuilder mailMessageSubject = new StringBuilder(jobExecutionContext.getJobDetail().getGroup()).append(": ").append(jobExecutionContext.getJobDetail().getName());
MailMessage mailMessage = new MailMessage();
mailMessage.setFromAddress(mailService.getBatchMailingList());
if (jobExecutionContext.getMergedJobDataMap().containsKey(REQUESTOR_EMAIL_ADDRESS_KEY) && !StringUtils.isBlank(jobExecutionContext.getMergedJobDataMap().getString(REQUESTOR_EMAIL_ADDRESS_KEY))) {
mailMessage.addToAddress(jobExecutionContext.getMergedJobDataMap().getString(REQUESTOR_EMAIL_ADDRESS_KEY));
}
if (SchedulerService.FAILED_JOB_STATUS_CODE.equals(jobStatus) || SchedulerService.CANCELLED_JOB_STATUS_CODE.equals(jobStatus)) {
mailMessage.addToAddress(mailService.getBatchMailingList());
}
mailMessageSubject.append(": ").append(jobStatus);
String messageText = MessageFormat.format(configurationService.getPropertyValueAsString(KFSKeyConstants.MESSAGE_BATCH_FILE_LOG_EMAIL_BODY), getLogFileName(NDC.peek()));
mailMessage.setMessage(messageText);
if (mailMessage.getToAddresses().size() > 0) {
mailMessage.setSubject(mailMessageSubject.toString());
mailService.sendMessage(mailMessage);
}
}
catch (Exception iae) {
LOG.error("Caught exception while trying to send job completion notification e-mail for " + jobExecutionContext.getJobDetail().getName(), iae);
}
}
/**
* @see org.quartz.JobListener#getName()
*/
@Override
public String getName() {
return NAME;
}
/**
* Sets the schedulerService attribute value.
*
* @param schedulerService The schedulerService to set.
*/
public void setSchedulerService(SchedulerService schedulerService) {
this.schedulerService = schedulerService;
}
/**
* Sets the configurationService attribute value.
*
* @param configurationService The configurationService to set.
*/
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
/**
* Sets the mailService attribute value.
*
* @param mailService The mailService to set.
*/
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
/**
* Sets the dateTimeService attribute value.
*
* @param dateTimeService The dateTimeService to set.
*/
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
}