Java tutorial
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; } } }