org.openmrs.scheduler.SchedulerServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.scheduler.SchedulerServiceTest.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.scheduler;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openmrs.api.context.Context;
import org.openmrs.scheduler.tasks.AbstractTask;
import org.openmrs.test.BaseContextSensitiveTest;
import org.openmrs.util.OpenmrsClassLoader;
import org.springframework.util.StringUtils;

/**
 * TODO test all methods in ScheduleService
 */
public class SchedulerServiceTest extends BaseContextSensitiveTest {

    private static Log log = LogFactory.getLog(SchedulerServiceTest.class);

    // so that we can guarantee tests running accurately instead of tests interfering with the next
    public final Integer SAVE_TASK_LOCK = new Integer(1);

    // each task provides a key that will be used in this map.  The value is the output
    private static Map<String, String> output = new HashMap<String, String>();

    @Before
    public void setUp() throws Exception {
        Context.flushSession();

        Collection<TaskDefinition> tasks = Context.getSchedulerService().getRegisteredTasks();
        for (TaskDefinition task : tasks) {
            Context.getSchedulerService().shutdownTask(task);
            Context.getSchedulerService().deleteTask(task.getId());
        }

        Context.flushSession();
    }

    @Test
    public void shouldResolveValidTaskClass() throws Exception {
        String className = "org.openmrs.scheduler.tasks.TestTask";
        Class c = OpenmrsClassLoader.getInstance().loadClass(className);
        Object o = c.newInstance();
        if (o instanceof Task)
            assertTrue("Class " + className + " is a valid Task", true);
        else
            fail("Class " + className + " is not a valid Task");
    }

    @Test(expected = ClassNotFoundException.class)
    public void shouldNotResolveInvalidClass() throws Exception {
        String className = "org.openmrs.scheduler.tasks.InvalidTask";
        Class c = OpenmrsClassLoader.getInstance().loadClass(className);
        Object o = c.newInstance();
        if (o instanceof Task)
            fail("Class " + className + " is not supposed to be a valid Task");
        else
            assertTrue("Class " + className + " is not a valid Task", true);
    }

    /**
     * Longer running class used to demonstrate tasks running concurrently
     */
    public static class ExecutePrintingTask extends AbstractTask {

        public void execute() {
            String outputKey = getTaskDefinition().getProperty("outputKey");
            appendOutput(outputKey, getTaskDefinition().getProperty("id"));

            try {
                Thread.sleep(Integer.valueOf(getTaskDefinition().getProperty("delay")));
            } catch (InterruptedException e) {
                log.error("Error generated", e);
            }

            appendOutput(outputKey, getTaskDefinition().getProperty("id"));

        }
    }

    /**
     * Helper method to append the given text to the "output" static variable map
     * <br/>
     * Map will contain string like "text, text1, text2"
     * 
     * @param outputKey the key for the "output" map
     * @param the text to append to the value in the output map with the given key
     */
    public synchronized static void appendOutput(String outputKey, String appendText) {
        if (StringUtils.hasLength(output.get(outputKey)))
            output.put(outputKey, output.get(outputKey) + ", " + appendText);
        else
            output.put(outputKey, appendText);
    }

    /**
     * Demonstrates concurrent running for tasks
     * 
     * <pre>
     *             |
     * SampleTask2 |    ----
     * SampleTask1 |------------
     *             |_____________ time
     *              ^   ^   ^   ^
     * Output:     S-1 S-2 E-2 E-1
     * </pre>
     */
    @Test
    public void shouldAllowTwoTasksToRunConcurrently() throws Exception {
        SchedulerService schedulerService = Context.getSchedulerService();

        TaskDefinition t1 = new TaskDefinition();
        t1.setId(1);
        t1.setStartOnStartup(false);
        t1.setStartTime(null);
        t1.setTaskClass(ExecutePrintingTask.class.getName());
        t1.setProperty("id", "TASK-1");
        t1.setProperty("delay", "400"); // must be longer than t2's delay
        t1.setProperty("outputKey", "shouldAllowTwoTasksToRunConcurrently");
        t1.setName("name");
        t1.setRepeatInterval(5000l);

        TaskDefinition t2 = new TaskDefinition();
        t2.setId(2);
        t2.setStartOnStartup(false);
        t2.setStartTime(null);
        t2.setTaskClass(ExecutePrintingTask.class.getName());
        t2.setProperty("id", "TASK-2");
        t2.setProperty("delay", "100"); // must be shorter than t1's delay
        t2.setProperty("outputKey", "shouldAllowTwoTasksToRunConcurrently");
        t2.setName("name");
        t2.setRepeatInterval(5000l);

        synchronized (SAVE_TASK_LOCK) {
            schedulerService.scheduleTask(t1);
            Thread.sleep(50); // so t2 doesn't start before t1 due to random millisecond offsets
            schedulerService.scheduleTask(t2);
            Thread.sleep(2500); // must be longer than t2's delay
            assertEquals("TASK-1, TASK-2, TASK-2, TASK-1", output.get("shouldAllowTwoTasksToRunConcurrently"));
        }
    }

    /**
     * Longer init'ing class for concurrent init test
     */
    public static class SimpleTask extends AbstractTask {

        public void initialize(TaskDefinition config) {
            String outputKey = config.getProperty("outputKey");
            appendOutput(outputKey, config.getProperty("id"));

            super.initialize(config);
            try {
                // must be less than delay before printing
                Thread.sleep(Integer.valueOf(config.getProperty("delay")));
            } catch (InterruptedException e) {
                log.error("Error generated", e);
            }

            appendOutput(outputKey, config.getProperty("id"));
        }

        public void execute() {
        }
    }

    /**
     * Demonstrates concurrent initializing for tasks
     * 
     * <pre>
     *             |
     * SampleTask4 |    ----
     * SampleTask3 |------------
     *             |_____________ time
     *              ^   ^   ^   ^
     * Output:     S-3 S-4 E-4 E-3
     * </pre>
     */
    @Test
    public void shouldAllowTwoTasksInitMethodsToRunConcurrently() throws Exception {
        SchedulerService schedulerService = Context.getSchedulerService();

        TaskDefinition t3 = new TaskDefinition();
        t3.setStartOnStartup(false);
        t3.setStartTime(null); // so it starts immediately
        t3.setTaskClass(SimpleTask.class.getName());
        t3.setProperty("id", "TASK-3");
        t3.setProperty("delay", "300"); // must be longer than t4's delay
        t3.setProperty("outputKey", "shouldAllowTwoTasksInitMethodsToRunConcurrently");
        t3.setName("name");
        t3.setRepeatInterval(5000l);

        TaskDefinition t4 = new TaskDefinition();
        t4.setStartOnStartup(false);
        t4.setStartTime(null); // so it starts immediately
        t4.setTaskClass(SimpleTask.class.getName());
        t4.setProperty("id", "TASK-4");
        t4.setProperty("delay", "100");
        t4.setProperty("outputKey", "shouldAllowTwoTasksInitMethodsToRunConcurrently");
        t4.setName("name");
        t4.setRepeatInterval(5000l);

        // both of these tasks start immediately
        synchronized (SAVE_TASK_LOCK) {
            schedulerService.scheduleTask(t3); // starts first, ends last
            schedulerService.scheduleTask(t4); // starts last, ends first
        }
        Thread.sleep(500); // must be greater than task3 delay so that it prints out its end
        assertEquals("TASK-3, TASK-4, TASK-4, TASK-3",
                output.get("shouldAllowTwoTasksInitMethodsToRunConcurrently"));

        // cleanup
        schedulerService.shutdownTask(t3);
        schedulerService.shutdownTask(t4);
    }

    public static class SampleTask5 extends AbstractTask {

        public void initialize(TaskDefinition config) {

            String outputKey = config.getProperty("outputKey");
            appendOutput(outputKey, "INIT-START-5");

            super.initialize(config);
            try {
                Thread.sleep(700);
            } catch (InterruptedException e) {
                log.error("Error generated", e);
            }

            appendOutput(outputKey, "INIT-END-5");
        }

        public void execute() {
            String outputKey = getTaskDefinition().getProperty("outputKey");
            appendOutput(outputKey, "IN EXECUTE");
        }
    }

    /**
     * Demonstrates that initialization of a task is accomplished before its execution without
     * interleaving, which is a non-trivial behavior in the presence of a threaded initialization
     * method (as implemented in TaskThreadedInitializationWrapper)
     * 
     * <pre>
     *             |
     * SampleTask5 |------------
     *             |_____________ time
     *              ^   ^   ^   ^
     * Output:     IS  IE   S   E
     * </pre>
     */
    @Test
    public void shouldNotAllowTaskExecuteToRunBeforeInitializationIsComplete() throws Exception {
        SchedulerService schedulerService = Context.getSchedulerService();

        TaskDefinition t5 = new TaskDefinition();
        t5.setId(5);
        t5.setStartOnStartup(false);
        t5.setStartTime(null); // immediate start
        t5.setTaskClass(SampleTask5.class.getName());
        t5.setProperty("outputKey", "shouldNotAllowTaskExecuteToRunBeforeInitializationIsComplete");
        t5.setName("name");
        t5.setRepeatInterval(5000l);

        synchronized (SAVE_TASK_LOCK) {
            schedulerService.scheduleTask(t5);
            Thread.sleep(2500);
            assertEquals("INIT-START-5, INIT-END-5, IN EXECUTE",
                    output.get("shouldNotAllowTaskExecuteToRunBeforeInitializationIsComplete"));
        }
    }

    @Test
    public void saveTask_shouldSaveTaskToTheDatabase() throws Exception {
        SchedulerService service = Context.getSchedulerService();

        TaskDefinition def = new TaskDefinition();
        final String TASK_NAME = "This is my test! 123459876";
        def.setName(TASK_NAME);
        def.setStartOnStartup(false);
        def.setRepeatInterval(10L);
        def.setTaskClass(ExecutePrintingTask.class.getName());

        synchronized (SAVE_TASK_LOCK) {
            int size = service.getRegisteredTasks().size();
            service.saveTask(def);
            Assert.assertEquals(size + 1, service.getRegisteredTasks().size());
        }

        def = service.getTaskByName(TASK_NAME);
        Assert.assertEquals(Context.getAuthenticatedUser().getUserId(), def.getCreator().getUserId());
    }

    /**
     * Sample task that does not extend AbstractTask
     */
    public static class BareTask implements Task {

        public static ArrayList outputList = new ArrayList();

        public void execute() {
            synchronized (outputList) {
                outputList.add("TEST");
            }
        }

        public TaskDefinition getTaskDefinition() {
            return null;
        }

        public void initialize(TaskDefinition definition) {
        }

        public boolean isExecuting() {
            return false;
        }

        public void shutdown() {
        }
    }

    /**
     * Task which does not return TaskDefinition in getTaskDefinition should run without throwing
     * exceptions.
     * 
     * @throws Exception
     */
    @Test
    public void shouldNotThrowExceptionWhenTaskDefinitionIsNull() throws Exception {
        SchedulerService schedulerService = Context.getSchedulerService();

        TaskDefinition td = new TaskDefinition();
        td.setId(10);
        td.setName("Task");
        td.setStartOnStartup(false);
        td.setTaskClass(BareTask.class.getName());
        td.setStartTime(null);
        td.setName("name");
        td.setRepeatInterval(5000l);

        synchronized (SAVE_TASK_LOCK) {
            schedulerService.scheduleTask(td);
        }
        Thread.sleep(500);

        assertTrue(BareTask.outputList.contains("TEST"));
    }

    /**
     * Task opens a session and stores the execution time.
     */
    public static class SessionTask extends AbstractTask {

        public void execute() {
            try {
                // something would happen here...

                actualExecutionTime = System.currentTimeMillis();
            } finally {
            }

        }
    }

    public static Long actualExecutionTime;

    /**
     * Check saved last execution time.
     */
    @Test
    public void shouldSaveLastExecutionTime() throws Exception {
        final String NAME = "Session Task";
        SchedulerService service = Context.getSchedulerService();

        TaskDefinition td = new TaskDefinition();
        td.setName(NAME);
        td.setStartOnStartup(false);
        td.setTaskClass(SessionTask.class.getName());
        td.setStartTime(null);
        td.setRepeatInterval(new Long(0));//0 indicates single execution
        synchronized (SAVE_TASK_LOCK) {
            service.saveTask(td);
            service.scheduleTask(td);
        }

        // refetch the task
        td = service.getTaskByName(NAME);

        // sleep a while until the task has executed, up to 30 times
        for (int x = 0; x < 30 && (actualExecutionTime == null || td.getLastExecutionTime() == null); x++)
            Thread.sleep(200);

        Assert.assertNotNull(
                "The actualExecutionTime variable is null, so either the SessionTask.execute method hasn't finished or didn't get run",
                actualExecutionTime);
        assertEquals("Last execution time in seconds is wrong", actualExecutionTime.longValue() / 1000,
                td.getLastExecutionTime().getTime() / 1000, 1);
    }
}