/*
* 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.service;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.sys.ConfigureContext;
import org.kuali.kfs.sys.batch.BatchJobStatus;
import org.kuali.kfs.sys.batch.Job;
import org.kuali.kfs.sys.batch.JobDescriptor;
import org.kuali.kfs.sys.batch.JobListener;
import org.kuali.kfs.sys.batch.SimpleTriggerDescriptor;
import org.kuali.kfs.sys.context.KualiTestBase;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.fixture.UserNameFixture;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
@ConfigureContext(session = UserNameFixture.kfs, initializeBatchSchedule = true)
public class SchedulerServiceImplTest extends KualiTestBase {
private String batchDirectory;
/**
* Creates the directory for the batch files to go in
* @see junit.framework.TestCase#setUp()
*/
@Override
protected void setUp() throws Exception {
super.setUp();
// not sure why we have to do this, but it seems to be necessary for the some of the tests to work (and jobs to actually be run)
Scheduler scheduler = (Scheduler) SpringContext.getService("scheduler");
if (!scheduler.isStarted()) {
scheduler.start();
}
batchDirectory = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString("staging.directory")+"/gl/test_directory/originEntry";
File batchDirectoryFile = new File(SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString("staging.directory")+"/gl/test_directory/");
batchDirectoryFile.mkdir();
batchDirectoryFile = new File(batchDirectory);
batchDirectoryFile.mkdir();
File nightlyOutFile = new File(batchDirectory + File.separator + GeneralLedgerConstants.BatchFileSystem.NIGHTLY_OUT_FILE);
if (!nightlyOutFile.exists()) {
nightlyOutFile.createNewFile();
}
}
/**
* Removes the directory for the batch files to go in
* @see junit.framework.TestCase#tearDown()
*/
@Override
public void tearDown() throws Exception {
File batchDirectoryFile = new File(batchDirectory);
for (File f : batchDirectoryFile.listFiles()) {
f.delete();
}
batchDirectoryFile.delete();
batchDirectoryFile = new File(SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString("staging.directory")+"/gl/test_directory");
batchDirectoryFile.delete();
}
// tests added to make sure that the scheduler was available during the tests
public void testGetJobs_unscheduled() {
SchedulerService s = SpringContext.getBean(SchedulerService.class);
List<BatchJobStatus> jobs = s.getJobs("unscheduled");
for (BatchJobStatus job : jobs) {
System.out.println(job);
}
}
public void testGetJobs_scheduled() {
SchedulerService s = SpringContext.getBean(SchedulerService.class);
List<BatchJobStatus> jobs = s.getJobs("scheduled");
for (BatchJobStatus job : jobs) {
System.out.println(job);
}
}
/*
* // this job seems to be missing from the system during the test runs public void testScheduledJobTrigger() throws Exception {
* SchedulerService s = SpringContext.getBean(SchedulerService.class); BatchJobStatus job = s.getJob( "scheduled", "scheduleJob"
* ); assertNotNull( "job must not be null", job ); System.out.println( "scheduleJob Next Run Time: " + job.getNextRunDate() );
* Calendar cal = Calendar.getInstance(); cal.setTime( job.getNextRunDate() ); assertEquals( "year must be 2099", 2099, cal.get(
* Calendar.YEAR ) ); }
*/
/**
* Test the running of a job. There is not much to test on the results, except that this does not cause an error.
*/
public void testRunJob() throws Exception {
SchedulerService s = SpringContext.getBean(SchedulerService.class);
BatchJobStatus job = s.getJob(SchedulerService.UNSCHEDULED_GROUP, "scrubberJob");
assertNotNull("job must not be null", job);
System.out.println(job);
job.runJob(null);
System.err.println( "testRunJob: Waiting for it to enter running status" );
// provide an "out" in case things fail badly
int waitCount = 0;
while (!job.isRunning() && waitCount < 500) {
Thread.sleep(100);
waitCount++;
}
System.out.println(s.getRunningJobs());
System.out.println(s.getJob(SchedulerService.UNSCHEDULED_GROUP, "scrubberJob"));
System.err.println( "testRunJob: Waiting for it to leave running status" );
waitCount = 0;
while (job.isRunning() && waitCount < 500) {
Thread.sleep(100);
waitCount++;
}
System.out.println(s.getRunningJobs());
System.out.println(s.getJob(SchedulerService.UNSCHEDULED_GROUP, "scrubberJob"));
}
/**
* Test that the unschedule job function works and removes the job from the standard scheduled group. Assumes: scrubberJob
* exists as a job in the scheduled group.
*/
public void testUnscheduleJob() throws Exception {
SchedulerService s = SpringContext.getBean(SchedulerService.class);
BatchJobStatus job = s.getJob(SchedulerService.SCHEDULED_GROUP, "autoDisapproveJob");
assertNotNull("job must not be null", job);
assertTrue("must return isScheduled == true", job.isScheduled());
s.removeScheduled(job.getName());
job = s.getJob(SchedulerService.SCHEDULED_GROUP, "autoDisapproveJob");
assertNull("new attempt to retrieve job must be null", job);
job = s.getJob(SchedulerService.UNSCHEDULED_GROUP, "autoDisapproveJob");
assertNotNull("job must not be null", job);
assertFalse("must return isScheduled == false", job.isScheduled());
}
/**
* Test that the schedule job function works and puts the job into the standard scheduled group. Also tests to make sure that
* BatchJobStatus detects the scheduled status even if it is in the unscheduled group. Assumes: clearOldOriginEntriesJob exists
* as a job in the unscheduled group.
*/
public void testScheduleJob() throws Exception {
SchedulerService s = SpringContext.getBean(SchedulerService.class);
BatchJobStatus job = s.getJob(SchedulerService.UNSCHEDULED_GROUP, "manualPurgeJob");
assertNotNull("job must not be null", job);
assertFalse("must return isScheduled == false", job.isScheduled());
job.schedule();
job = s.getJob(SchedulerService.UNSCHEDULED_GROUP, "manualPurgeJob");
assertNotNull("job (in unsched group) must not be null", job);
assertTrue("must return isScheduled == true", job.isScheduled());
job = s.getJob(SchedulerService.SCHEDULED_GROUP, "manualPurgeJob");
assertNotNull("job (in sched group) must not be null", job);
assertTrue("must return isScheduled == true", job.isScheduled());
}
protected void scheduleJob(String groupName, String jobName, int startStep, int endStep, Date startTime, String requestorEmailAddress, Map<String,String> additionalJobData ) {
Scheduler scheduler = (Scheduler) SpringContext.getService("scheduler");
try {
JobDetail jobDetail = scheduler.getJobDetail(jobName, groupName);
if ( jobDetail == null ) {
fail( "Unable to retrieve JobDetail object for " + groupName + " : " + jobName );
}
if ( jobDetail.getJobDataMap() == null ) {
jobDetail.setJobDataMap( new JobDataMap() );
}
jobDetail.getJobDataMap().put(SchedulerService.JOB_STATUS_PARAMETER, SchedulerService.SCHEDULED_JOB_STATUS_CODE);
scheduler.addJob(jobDetail, true);
SimpleTriggerDescriptor trigger = new SimpleTriggerDescriptor(jobName+startTime, groupName, jobName, SpringContext.getBean(DateTimeService.class));
trigger.setStartTime(startTime);
Trigger qTrigger = trigger.getTrigger();
qTrigger.getJobDataMap().put(JobListener.REQUESTOR_EMAIL_ADDRESS_KEY, requestorEmailAddress);
qTrigger.getJobDataMap().put(Job.JOB_RUN_START_STEP, String.valueOf(startStep));
qTrigger.getJobDataMap().put(Job.JOB_RUN_END_STEP, String.valueOf(endStep));
if ( additionalJobData != null ) {
qTrigger.getJobDataMap().putAll(additionalJobData);
}
scheduler.scheduleJob(qTrigger);
}
catch (SchedulerException e) {
throw new RuntimeException("Caught exception while scheduling job: " + jobName, e);
}
}
/*
* This test has problems with timing. It needs a job that it can rely on running for a specified period of time before being
* included in the automated tests.
*/
public void testJobInterrupt() throws Exception {
// need to clear out the dependencies
JobDescriptor jd = SpringContext.getBean(JobDescriptor.class, "scrubberJob");
jd.getDependencies().clear();
SchedulerService s = SpringContext.getBean(SchedulerService.class);
// need to ensure that the job does not already have a status (from prior tests)
// s.updateStatus(jd.getJobDetail(), null);
System.err.println( "About to run scrubber job in testJobInterrupt" );
scheduleJob(SchedulerService.SCHEDULED_GROUP, "scrubberJob", 0, 0, new Date(), null, null);
BatchJobStatus job = s.getJob(SchedulerService.SCHEDULED_GROUP, "scrubberJob");
// wait for job to enter running status
int waitCount = 0;
System.err.println( "Waiting for it to enter running status" );
// provide an "out" in case things fail badly
while (!job.isRunning() && waitCount < 500) {
Thread.sleep(20);
waitCount++;
}
// stop the job
System.err.println( "Interrupting the job" );
job.interrupt();
waitCount = 0;
System.err.println( "Waiting for it to exit running status" );
while (job.isRunning() && waitCount < 500) {
Thread.sleep(20);
waitCount++;
}
Thread.sleep(2000);
job = s.getJob(SchedulerService.SCHEDULED_GROUP, "scrubberJob");
List<BatchJobStatus> jobs = s.getJobs();
for (BatchJobStatus b : jobs) {
if ( b.getName().equals( "scrubberJob" ) ) {
System.out.println(b);
}
}
assertEquals("job status not correct", SchedulerService.CANCELLED_JOB_STATUS_CODE, job.getStatus());
}
public void test2ndExecutionOfJobAfterJobInterrupt() throws Exception {
JobDescriptor jd = SpringContext.getBean(JobDescriptor.class, "scrubberJob");
SchedulerService s = SpringContext.getBean(SchedulerService.class);
// need to ensure that the job does not already have a status
// s.updateStatus(jd.getJobDetail(), null);
// this will put scrubberJob into a Cancelled state
// We need to give this next part 30 seconds - scheduling this for a future execution
Date secondRunTime = new Date( System.currentTimeMillis() + 30000L );
scheduleJob(SchedulerService.SCHEDULED_GROUP, "scrubberJob", 0, 0, secondRunTime, null, null);
testJobInterrupt();
// Ensure it is properly cancelled
BatchJobStatus job = s.getJob(SchedulerService.SCHEDULED_GROUP, "scrubberJob");
assertEquals("job status not correct", SchedulerService.CANCELLED_JOB_STATUS_CODE, job.getStatus());
// now, wait until the next run is supposed to start
if ( secondRunTime.getTime() - System.currentTimeMillis() > 0 ) {
Thread.sleep( secondRunTime.getTime() - System.currentTimeMillis() );
}
// now, we try to run the job again, previously, this would result in the job being in a Cancelled state and refusing to run again
System.err.println( "Attempting to run the job a 2nd time" );
for (BatchJobStatus b : s.getJobs() ) {
if ( b.getName().equals( "scrubberJob" ) ) {
System.err.println(b);
}
}
System.err.println( "Waiting for it to enter running status" );
// provide an "out" in case things fail badly
int waitCount = 0;
while (!job.isRunning() && waitCount < 100) {
Thread.sleep(50);
waitCount++;
}
job = s.getJob(SchedulerService.SCHEDULED_GROUP, "scrubberJob");
if ( StringUtils.equals( SchedulerService.CANCELLED_JOB_STATUS_CODE, job.getStatus() ) ) {
fail( "Job should not have been in cancelled status");
}
assertEquals("job status not correct", SchedulerService.RUNNING_JOB_STATUS_CODE, job.getStatus());
}
/**
* Verify that dropDependenciesNotScheduled drops unscheduled dependencies. It's worthwhile to note that spring-sys-test-xml was altered to include a fake
* dependency of purgeReportsAndStagingJob on dailyEmailJob. This fake dependency was added to have a scheduled job dependend on an unscheduled one so that
* we have something to test for.
* @throws Exception
*/
public void testDropDependenciesNotScheduled() throws Exception {
SchedulerService schedulerService = SpringContext.getBean(SchedulerService.class);
BatchJobStatus purgeReportsAndStagingJob = schedulerService.getJob(SchedulerService.SCHEDULED_GROUP, "purgeReportsAndStagingJob");
for (Entry<String, String> dependency : purgeReportsAndStagingJob.getDependencies().entrySet()) {
String dependencyJobName = dependency.getKey();
assertFalse("Job was expected to be unscheduled so dropDependenciesNotScheduled should have removed this dependency.", "dailyEmailJob".equals(dependencyJobName));
}
}
public void testCronConditionMet() {
SchedulerService schedulerService = SpringContext.getBean(SchedulerService.class);
//Set year to 2199 for this cron expression so it won't match, at least not for almost two centuries
// and if KFS is still running by then, I'm guessing there are larger concerns than just this test.
String cronExpression = "0 * * ? * 5#3 2199";
assertFalse(schedulerService.cronConditionMet(cronExpression));
// The next valid date for this cron expression should be today so it should always match
cronExpression = "0 * * ? * 1-7";
assertTrue(schedulerService.cronConditionMet(cronExpression));
}
}