Coverage Summary for Class: TaskManager (org.kitodo.production.helper.tasks)

Class Class, % Method, % Line, %
TaskManager 100% (1/1) 63,6% (7/11) 58,5% (31/53)


 /*
  * (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
  *
  * This file is part of the Kitodo project.
  *
  * It is licensed under GNU General Public License version 3 or later.
  *
  * For the full copyright and license information, please read the
  * GPL3-License.txt file that was distributed with this source code.
  */
 
 package org.kitodo.production.helper.tasks;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.ConcurrentModificationException;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import org.kitodo.config.ConfigCore;
 import org.kitodo.config.enums.ParameterCore;
 import org.kitodo.production.helper.tasks.EmptyTask.Behaviour;
 
 /**
  * The class TaskManager serves to handle the execution of threads. It can be
  * user controlled by the “Long running task manager”, backed by
  * {@link org.kitodo.production.forms.TaskManagerForm}.
  */
 public class TaskManager {
 
     /**
      * The field singletonInstance holds the singleton instance of the
      * TaskManager. Tough the method signatures of TaskManager are static, it is
      * implemented as singleton internally. All accesses to the instance must be
      * done by calling the synchronized function singleton() or concurrency
      * issues may arise.
      */
     private static TaskManager singletonInstance;
 
     /**
      * The field taskSitter holds a scheduled executor to repeatedly run the
      * TaskSitter task which will remove old threads and start new ones as
      * configured to do.
      */
     private final ScheduledExecutorService taskSitter;
 
     /**
      * The field taskList holds the list of threads managed by the task manager.
      */
     final LinkedList<EmptyTask> taskList = new LinkedList<>();
 
     /**
      * TaskManager is a singleton so its constructor is private. It will be
      * called once and just once by the synchronized function singleton() and
      * set up a housekeeping thread.
      */
     private TaskManager() {
         taskSitter = Executors.newSingleThreadScheduledExecutor();
         long delay = ConfigCore.getLongParameterOrDefaultValue(ParameterCore.TASK_MANAGER_INSPECTION_INTERVAL_MILLIS);
         taskSitter.scheduleWithFixedDelay(new TaskSitter(), delay, delay, TimeUnit.MILLISECONDS);
     }
 
     /**
      * Adds a task thread to the task list.
      *
      * @param task
      *            task to add
      */
     public static void addTask(EmptyTask task) {
         singleton().taskList.addLast(task);
     }
 
     /**
      * The procedure will add a task to the task list if it
      * has not yet been added right after the last task that is currently
      * executing. If this fails for some reason (i.e. the list got concurrently
      * modified) it will be added in the end.
      *
      * <p>
      * This is a fallback method that is called by the overloaded start() method
      * of AbstractTask. Do not use it. Use TaskManager.addTask() to properly add
      * the tasks you created.
      *
      * @param task
      *            task to add
      */
     static void addTaskIfMissing(EmptyTask task) {
         LinkedList<EmptyTask> tasks = singleton().taskList;
         if (!tasks.contains(task)) {
             int pos = lastIndexOf(TaskState.WORKING) + 1;
             try {
                 tasks.add(pos, task);
             } catch (IndexOutOfBoundsException e) {
                 tasks.addLast(task);
             }
         }
     }
 
     /**
      * Returns a copy of the task list usable for
      * displaying. The result object cannot be used to modify the list. Use
      * removeAllFinishedTasks() to clean up the list or stopAndDeleteAllTasks()
      * if you wish to do so. To get rid of one specific task, call
      * abstractTask.interrupt(Behaviour.DELETE_IMMEDIATELY) which will cause it
      * to be removed by the TaskSitter as soon as it has terminated
      * successfully.
      *
      * @return a copy of the task list
      */
     public static List<EmptyTask> getTaskList() {
         return new ArrayList<>(singleton().taskList);
     }
 
     /**
      * Returns the index of the last task in the task
      * list that is in the given TaskState.
      *
      * @param state
      *            state of tasks to look for
      * @return the index of the last task in that state
      */
     private static int lastIndexOf(TaskState state) {
         int lastIndex = -1;
         int pos = -1;
         for (EmptyTask task : singleton().taskList) {
             pos++;
             if (task.getTaskState().equals(state)) {
                 lastIndex = pos;
             }
         }
         return lastIndex;
     }
 
     /**
      * Can be called to remove all
      * terminated threads from the list.
      */
     public static void removeAllFinishedTasks() {
         boolean redo;
         do {
             redo = false;
             try {
                 singleton().taskList.removeIf(emptyTask -> emptyTask.getState().equals(Thread.State.TERMINATED));
             } catch (ConcurrentModificationException listModifiedByAnotherThreadWhileIterating) {
                 redo = true;
             }
         } while (redo);
     }
 
     /**
      * Can be called to move a task by one forwards on
      * the queue.
      *
      * @param task
      *            task to move forwards
      */
     public static void runEarlier(EmptyTask task) {
         TaskManager theManager = singleton();
         int index = theManager.taskList.indexOf(task);
         if (index > 0) {
             Collections.swap(theManager.taskList, index - 1, index);
         }
     }
 
     /**
      * Can be called to move a task by one backwards on
      * the queue.
      *
      * @param task
      *            task to move backwards
      */
     public static void runLater(EmptyTask task) {
         TaskManager theManager = singleton();
         int index = theManager.taskList.indexOf(task);
         if (index > -1 && index + 1 < theManager.taskList.size()) {
             Collections.swap(theManager.taskList, index, index + 1);
         }
     }
 
     /**
      * The synchronized function singleton() must be used to obtain singleton
      * access to the TaskManager instance.
      *
      * @return the singleton TaskManager instance
      */
     static synchronized TaskManager singleton() {
         if (Objects.isNull(singletonInstance)) {
             singletonInstance = new TaskManager();
         }
         return singletonInstance;
     }
 
     /**
      * The function will be called by the TaskSitter to gracefully
      * exit the task manager as well as its managed threads during container
      * shutdown.
      */
     static void shutdownNow() {
         stopAndDeleteAllTasks();
         singleton().taskSitter.shutdownNow();
     }
 
     /**
      * Can be called to both request
      * interrupt and immediate deletion for all threads that are alive and at
      * the same time remove all threads that aren’t alive anyhow.
      */
     public static void stopAndDeleteAllTasks() {
         boolean redo;
         do {
             redo = false;
             try {
                 Iterator<EmptyTask> inspector = singleton().taskList.iterator();
                 while (inspector.hasNext()) {
                     EmptyTask task = inspector.next();
                     if (task.isAlive()) {
                         task.interrupt(Behaviour.DELETE_IMMEDIATELY);
                     } else {
                         inspector.remove();
                     }
                 }
             } catch (ConcurrentModificationException listModifiedByAnotherThreadWhileIterating) {
                 redo = true;
             }
         } while (redo);
     }
 }