package ca.uhn.fhir.jpa.sched;

import ca.uhn.fhir.jpa.model.sched.HapiJob;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.AopTestUtils;

import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

@ContextConfiguration(classes = SchedulerServiceImplTest.TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SchedulerServiceImplTest {

	private static final Logger ourLog = LoggerFactory.getLogger(SchedulerServiceImplTest.class);

	@Autowired
	private ISchedulerService mySvc;
	private static long ourTaskDelay;

	@Before
	public void before() {
		ourTaskDelay = 0;
	}

	@Test
	public void testScheduleTask() {

		ScheduledJobDefinition def = new ScheduledJobDefinition()
			.setId(CountingJob.class.getName())
			.setJobClass(CountingJob.class);

		mySvc.scheduleLocalJob(100, def);

		sleepAtLeast(1000);

		ourLog.info("Fired {} times", CountingJob.ourCount);

		assertThat(CountingJob.ourCount, greaterThan(3));
		assertThat(CountingJob.ourCount, lessThan(20));
	}

	@Test
	public void testStopAndStartService() throws SchedulerException {

		ScheduledJobDefinition def = new ScheduledJobDefinition()
			.setId(CountingJob.class.getName())
			.setJobClass(CountingJob.class);

		BaseSchedulerServiceImpl svc = AopTestUtils.getTargetObject(mySvc);
		svc.stop();
		svc.create();
		svc.start();

		mySvc.scheduleLocalJob(100, def);

		sleepAtLeast(1000);

		ourLog.info("Fired {} times", CountingJob.ourCount);

		assertThat(CountingJob.ourCount, greaterThan(3));
		assertThat(CountingJob.ourCount, lessThan(20));
	}

	@Test
	public void testScheduleTaskLongRunningDoesntRunConcurrently() {

		ScheduledJobDefinition def = new ScheduledJobDefinition()
			.setId(CountingJob.class.getName())
			.setJobClass(CountingJob.class);
		ourTaskDelay = 500;

		mySvc.scheduleLocalJob(100, def);

		sleepAtLeast(1000);

		ourLog.info("Fired {} times", CountingJob.ourCount);

		assertThat(CountingJob.ourCount, greaterThanOrEqualTo(1));
		assertThat(CountingJob.ourCount, lessThan(5));
	}

	@Test
	public void testIntervalJob() {

		ScheduledJobDefinition def = new ScheduledJobDefinition()
			.setId(CountingIntervalJob.class.getName())
			.setJobClass(CountingIntervalJob.class);
		ourTaskDelay = 500;

		mySvc.scheduleLocalJob(100, def);

		sleepAtLeast(2000);

		ourLog.info("Fired {} times", CountingIntervalJob.ourCount);

		assertThat(CountingIntervalJob.ourCount, greaterThanOrEqualTo(2));
		assertThat(CountingIntervalJob.ourCount, lessThan(6));
	}

	@After
	public void after() throws SchedulerException {
		CountingJob.ourCount = 0;
		CountingIntervalJob.ourCount = 0;
		mySvc.purgeAllScheduledJobsForUnitTest();
	}

	@DisallowConcurrentExecution
	public static class CountingJob implements Job, ApplicationContextAware {

		private static int ourCount;

		@Autowired
		@Qualifier("stringBean")
		private String myStringBean;
		private ApplicationContext myAppCtx;

		@Override
		public void execute(JobExecutionContext theContext) {
			if (!"String beans are good.".equals(myStringBean)) {
				fail("Did not autowire stringBean correctly, found: " + myStringBean);
			}
			if (myAppCtx == null) {
				fail("Did not populate appctx");
			}
			if (ourTaskDelay > 0) {
				ourLog.info("Job has fired, going to sleep for {}ms", ourTaskDelay);
				sleepAtLeast(ourTaskDelay);
				ourLog.info("Done sleeping");
			} else {
				ourLog.info("Job has fired...");
			}
			ourCount++;
		}

		@Override
		public void setApplicationContext(ApplicationContext theAppCtx) throws BeansException {
			myAppCtx = theAppCtx;
		}
	}

	public static class CountingIntervalJob implements HapiJob {

		private static int ourCount;

		@Autowired
		@Qualifier("stringBean")
		private String myStringBean;
		private ApplicationContext myAppCtx;

		@Override
		public void execute(JobExecutionContext theContext) {
				ourLog.info("Job has fired, going to sleep for {}ms", ourTaskDelay);
				sleepAtLeast(ourTaskDelay);
			ourCount++;
		}
	}

	@Configuration
	public static class TestConfiguration {

		@Bean
		public ISchedulerService schedulerService() {
			return new HapiSchedulerServiceImpl();
		}

		@Bean
		public String stringBean() {
			return "String beans are good.";
		}

		@Bean
		public AutowiringSpringBeanJobFactory springBeanJobFactory() {
			return new AutowiringSpringBeanJobFactory();
		}

	}
}

@@ -1160,27 +1160,188 @@
 org.
-hamcrest.Matchers.*
+awaitility.Awaitility.await;%0Aimport static org.hamcrest.Matchers.greaterThan;%0Aimport static org.hamcrest.Matchers.greaterThanOrEqualTo;%0Aimport static org.hamcrest.Matchers.lessThan
 ;%0Aim
@@ -1677,17 +1677,51 @@
 class);%0A
+%09private static long ourTaskDelay;
 %0A
-
 %09@Autowi
@@ -1761,43 +1761,8 @@
 Svc;
-%0A%09private static long ourTaskDelay;
 %0A%0A%09@
@@ -2658,33 +2658,43 @@
 Count);%0A%0A%09%09a
-ssertThat
+wait().until(() -%3E 
 (CountingJob
@@ -3122,33 +3122,43 @@
 Count);%0A%0A%09%09a
-ssertThat
+wait().until(() -%3E 
 (CountingJob
@@ -3585,34 +3585,43 @@
 Count);%0A%0A%09%09a
-ssertThat(
+wait().until(() -%3E 
 CountingInte
@@ -5065,25 +5065,24 @@
 ntext) %7B%0A%09%09%09
-%09
 ourLog.info(
@@ -5130,33 +5130,32 @@
  ourTaskDelay);%0A
-%09
 %09%09%09sleepAtLeast(

Fix intermittent