tv.arte.resteventapi.core.scheduling.RestEventScheduledThreadPoolExecutorScheduler.java Source code

Java tutorial

Introduction

Here is the source code for tv.arte.resteventapi.core.scheduling.RestEventScheduledThreadPoolExecutorScheduler.java

Source

package tv.arte.resteventapi.core.scheduling;

/*
 * #%L
 * RestEventAPI
 * %%
 * Copyright (C) 2014 ARTE G.E.I.E
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of The MIT License (MIT) as published by the Open Source 
 * Initiative.
 * 
 * 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 
 * MIT License (MIT) for more details.
 * 
 * You should have received a copy of The MIT License (MIT) 
 * along with this program.  If not, see <http://opensource.org/licenses/MIT>
 * #L%
 */

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import tv.arte.resteventapi.core.RestEventApiRuntimeException;
import tv.arte.resteventapi.core.domain.entities.RestEvent;
import tv.arte.resteventapi.core.domain.entities.enums.RestEventStatus;
import tv.arte.resteventapi.core.domain.holders.RestEventHolder;
import tv.arte.resteventapi.core.services.EventService;

/**
 * An {@link RestEvent} scheduler and executor 
 * 
 * @author Simeon Petev
 * @since 0.1
 */
public class RestEventScheduledThreadPoolExecutorScheduler implements RestEventScheduler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private EventService eventService;

    /**
     * Number of seconds between consecutive rescheduling of the rest events
     * 
     * The minutes equivalent of this value should be much smaller than the value of {@link #maxMinutesToPrefetchScheduledEvents}
     */
    private int secondsBetweenRescheduling = 60;

    /**
     * The maximum number of minutes to prefetch the scheduled events
     */
    private int maxMinutesToPrefetchScheduledEvents = 60;

    /**
     * The executor dedicated to processing {@link RestEvent}
     */
    private ScheduledThreadPoolExecutor restEventsScheduledExecutor;

    /**
     * Executes only one task that is responsible for scheduling tasks for {@link #restEventsScheduledExecutor}
     */
    private ScheduledExecutorService reschedulingExecutor;

    /**
     * A map with all task that are currently scheduled
     * key = id of the rest event
     * value = ScheduledFuture object
     */
    private Map<String, ScheduledFuture<?>> nonExecutedSheduledTasks = new ConcurrentHashMap<String, ScheduledFuture<?>>(
            3000);

    private Map<String, ScheduledFuture<?>> currentlyExecutedTasks = new ConcurrentHashMap<String, ScheduledFuture<?>>(
            3000);

    /**
     * A map associating a given runnable to an id of a rest event
     */
    private Map<Object, String> namedRunnablesCallables = new ConcurrentHashMap<Object, String>(3000);

    /**
     * @see ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int, RejectedExecutionHandler)
     */
    public RestEventScheduledThreadPoolExecutorScheduler(int corePoolSize, RejectedExecutionHandler handler) {
        this(corePoolSize, Executors.defaultThreadFactory(), handler, null, null);
    }

    /**
     * @see ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int, ThreadFactory, RejectedExecutionHandler)
     */
    public RestEventScheduledThreadPoolExecutorScheduler(int corePoolSize, ThreadFactory threadFactory,
            RejectedExecutionHandler handler, Integer secondsBetweenRescheduling,
            Integer maxMinutesToPrefetchScheduledEvents) {

        if (secondsBetweenRescheduling != null) {
            if (secondsBetweenRescheduling < 0) {
                throw new RestEventApiRuntimeException(
                        "secondsBetweenRescheduling should be a positive integer value");
            }
            this.secondsBetweenRescheduling = secondsBetweenRescheduling;
        }
        if (maxMinutesToPrefetchScheduledEvents != null) {
            if (maxMinutesToPrefetchScheduledEvents < 0) {
                throw new RestEventApiRuntimeException(
                        "maxMinutesToPrefetchScheduledEvents should be a positive integer value");
            }
            this.maxMinutesToPrefetchScheduledEvents = maxMinutesToPrefetchScheduledEvents;
        }
        this.reschedulingExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
        this.reschedulingExecutor.scheduleWithFixedDelay(new ReschedulingTask(), 30,
                this.secondsBetweenRescheduling, TimeUnit.SECONDS);

        this.restEventsScheduledExecutor = new ScheduledThreadPoolExecutor(corePoolSize, threadFactory, handler);
    }

    /**
     * @see ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int, ThreadFactory)
     */
    public RestEventScheduledThreadPoolExecutorScheduler(int corePoolSize, ThreadFactory threadFactory) {
        this(corePoolSize, threadFactory, new ThreadPoolExecutor.DiscardPolicy(), null, null);
    }

    /**
     * Defines the scheduler configuring the amount of minutes between automatic rescheduling 
     * 
     * @see ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int)
     */
    public RestEventScheduledThreadPoolExecutorScheduler(int corePoolSize, Integer secondsBetweenRescheduling,
            Integer maxMinutesToPrefetchScheduledEvents) {
        this(corePoolSize, Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy(),
                secondsBetweenRescheduling, maxMinutesToPrefetchScheduledEvents);
    }

    /**
     * @see ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int)
     */
    public RestEventScheduledThreadPoolExecutorScheduler(int corePoolSize) {
        this(corePoolSize, Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy(), null, null);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isScheduled(String id) {
        return this.nonExecutedSheduledTasks.containsKey(id);
    }

    /**
     * {@inheritDoc}
     */
    public boolean unschedule(String id) {
        if (logger.isDebugEnabled())
            logger.debug("Unscheduling RestEvent with id: " + id);

        boolean unscheduled = false;

        if (this.nonExecutedSheduledTasks.containsKey(id)) {
            unscheduled = this.nonExecutedSheduledTasks.get(id).cancel(false);

            if (unscheduled) {
                this.nonExecutedSheduledTasks.remove(id);
                Object objectToRemove = null;
                for (Entry<Object, String> runnableEntry : namedRunnablesCallables.entrySet()) {
                    if (runnableEntry.getValue().equals(id)) {
                        objectToRemove = runnableEntry.getKey();
                        break;
                    }
                }
                namedRunnablesCallables.remove(objectToRemove);
            } else {
                logger.warn("Cannot cancel scheduled task for RestEvent with id: " + id);
            }
        } else if (this.currentlyExecutedTasks.containsKey(id)) {
            logger.warn("Cannot unschedule. The RestEvent with id: " + id + " is being currently processed");
        } else {
            if (logger.isDebugEnabled())
                logger.debug("No scheduled RestEvent found with id: " + id);
        }

        return unscheduled;
    }

    /**
     * {@inheritDoc}
     */
    public boolean schedule(RestEvent restEvent) {
        boolean scheduled = false;

        if (nonExecutedSheduledTasks.containsKey(restEvent.getId())) {
            if (logger.isDebugEnabled())
                logger.debug(
                        "Will not reschedule RestEvent with id " + restEvent.getId() + ". It's already scheduled.");
            return scheduled;
        }

        if (currentlyExecutedTasks.containsKey(restEvent.getId())) {
            if (logger.isDebugEnabled())
                logger.debug("Will not reschedule RestEvent with id " + restEvent.getId()
                        + ". It's being currently processed.");
            return scheduled;
        }

        DateTime immutableNow = DateTime.now();
        DateTime taskDate = new DateTime(restEvent.getNextExecution());
        long numberOfSeconds = 0L;

        if (taskDate.isAfter(immutableNow)) {
            numberOfSeconds = new Interval(immutableNow.getMillis(), taskDate.getMillis()).toDuration()
                    .getStandardSeconds();
        }

        doSchedule(restEvent.getId(), new RestEventProcessingTask(restEvent.getId()), numberOfSeconds,
                TimeUnit.SECONDS);
        scheduled = true;

        return scheduled;
    }

    /**
     * Schedule a named runnable
     * 
     * @see ScheduledThreadPoolExecutor#schedule(Runnable, long, TimeUnit)
     */
    protected ScheduledFuture<?> doSchedule(String id, Runnable command, long delay, TimeUnit unit) {

        if (id == null) {
            throw new RestEventApiRuntimeException("The id of a rest event could not be null");
        }

        ScheduledFuture<?> future = this.restEventsScheduledExecutor.schedule(command, delay, unit);

        this.nonExecutedSheduledTasks.put(id, future);
        this.namedRunnablesCallables.put(command, id);

        return future;
    }

    /**
     * A runnable task that will just reschedule some tasks
     * 
     * @author Simeon Petev
     * @since 0.1
     */
    private class ReschedulingTask implements Runnable {
        public void run() {
            if (logger.isDebugEnabled())
                logger.debug("Rescheduling RestEvents...");

            List<RestEvent> scheduledEvents = eventService
                    .eventsForScheduling(DateTime.now().plusMinutes(maxMinutesToPrefetchScheduledEvents).toDate());
            for (RestEvent scheduledEvent : scheduledEvents) {
                schedule(scheduledEvent);
            }

            if (logger.isDebugEnabled())
                logger.debug(
                        "A total of " + scheduledEvents.size() + " RestEvent has been determined for scheduling");
        }
    }

    /**
     * A task for single {@link RestEvent} processing
     * 
     * @author Simeon Petev
     * @since 0.1
     */
    private class RestEventProcessingTask implements Runnable, RestEventHolder {

        /**
         * The {@link RestEvent} id
         */
        private String id;

        /**
         * The restEvent after successful execution
         */
        private RestEvent restEvent;

        /**
         * Constructor
         * 
         * @param id {@link RestEvent} id
         */
        public RestEventProcessingTask(String id) {
            this.id = id;
        }

        public void run() {
            /*
             * Do some pre-processing tasks
             */
            if (logger.isDebugEnabled())
                logger.debug("Preparing for RestEvent pre-processing ... ");

            ScheduledFuture<?> scheduledFuture = nonExecutedSheduledTasks.remove(id);
            currentlyExecutedTasks.put(id, scheduledFuture);

            if (logger.isDebugEnabled())
                logger.debug("Finished RestEvent pre-processing. Id: " + id);

            /*
             * Do processing
             */
            try {
                this.restEvent = eventService.processRestEvent(id);
            } catch (Exception e) {
                if (logger.isDebugEnabled())
                    logger.debug(ExceptionUtils.getFullStackTrace(e));
            }

            /*
             * Do some post-processing tasks
             */
            if (logger.isDebugEnabled())
                logger.debug("Preparing for RestEvent post-processing ... ");

            nonExecutedSheduledTasks.remove(id);

            if (this.restEvent != null) {
                int minutesToFetchInFuture = RestEventScheduledThreadPoolExecutorScheduler.this.maxMinutesToPrefetchScheduledEvents;
                long eventNextExecution = this.restEvent.getNextExecution().getTime();
                int executionsCounter = this.restEvent.getCounter();
                int maxExecutions = this.restEvent.getMaxAttempts();

                //Automatically reschedule the event if applicable
                if (DateTime.now().plusMinutes(minutesToFetchInFuture).isAfter(eventNextExecution)
                        && executionsCounter < maxExecutions
                        && !RestEventStatus.SENT.equals(this.restEvent.getStatus())) {
                    currentlyExecutedTasks.remove(id);
                    RestEventScheduledThreadPoolExecutorScheduler.this.schedule(this.restEvent);
                    if (logger.isDebugEnabled())
                        logger.debug("RestEvent rescheduled immediately after competition. Id: " + id);
                }
            }

            if (logger.isDebugEnabled())
                logger.debug("Finished RestEvent post-processing. Id: " + id);
        }

        /**
         * {@inheritDoc}
         */
        public String getRestEventId() {
            return this.id;
        }

        /**
         * {@inheritDoc}
         */
        public RestEvent getRestEvent() {
            return this.restEvent;
        }
    }

}