All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBusx.util.ETimer Maven / Gradle / Ivy

There is a newer version: 7.6.0
Show newest version
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library 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 GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright 2012, 2015, 2016. Charles W. Rapp
// All Rights Reserved.
//

package net.sf.eBusx.util;

import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.client.EFeed;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.EFeedState;
import net.sf.eBus.client.EReplier;
import net.sf.eBus.client.EReplyFeed;
import net.sf.eBus.client.EReplyFeed.ERequest;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.EReplyMessage.ReplyStatus;
import net.sf.eBus.messages.type.DataType;

/**
 * This class provides an eBus request/reply interface on top of
 * the Java {@link java.util.Timer} class. This eBus interface
 * provides applications a uniform event interface: objects
 * receive events as messages from the eBus API. If the Java
 * timer interface is used directly, then timer events must be
 * manually integrated into the event-processing logic.
 * 

* This interface is not to be preferred if timer tasks are used * autonomously rather than as events delivered to an eBus * client. In that case, the Java {@link java.util.TimerTask} * should be used directly. *

*

* {@code ETimer} service must be started by an application by * calling one of the {@code startETimer} methods before * application objects can successfully subscribe to the timer * server. {@code ETimer} service is local to the JVM only. * Requestors cannot access a remote eBus timer service. *

*

* See {@link net.sf.eBusx.util package documentation} for sample * code. *

* * @author Charles W. Rapp */ public final class ETimer implements EReplier { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * The default timer name is "ETimer". */ public static final String DEFAULT_TIMER_NAME = "ETimer"; /** * The advertised request message subject is "/eBusTimer". */ public static final String TIMER_SUBJECT = "/eBusTimer"; /** * The advertised request message key is * TimerRequest:/eBusTimer. */ public static final EMessageKey TIMER_KEY = new EMessageKey(TimerRequest.class, TIMER_SUBJECT); //----------------------------------------------------------- // Statics. // /** * The singleton ETimer instance. */ private static ETimer sInstance = null; /** * The singleton lock. */ private static final Lock sLock = new ReentrantLock(true); /** * Set this signal when either {@link #startup()} completes. */ private static final Condition sStartSignal = sLock.newCondition(); /** * Set this signal when either {@link #shutdown()} completes. */ private static final Condition sStopSignal = sLock.newCondition(); /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger((ETimer.class).getName()); //----------------------------------------------------------- // Locals. // /** * {@code ETimer} name. Used for logging. */ private final String mName; /** * The Java timer thread. */ private final Timer mTimer; /** * The currently running timer requests. */ private final Map mTasks; /** * The timer service feed. */ private EReplyFeed mTimerFeed; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new ETimer instance with the given name and * daemon flag. * @param name the timer thread name. * @param isDaemon {@code true} means the timer is a daemon * thread. */ private ETimer(final String name, final boolean isDaemon) { mName = name; mTimer = new Timer(name, isDaemon); mTasks = new HashMap<>(); mTimerFeed = null; } // end of ETimer(String, boolean) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // EObject Interface Implementation. // /** * Puts the timer service request advertisement in place. */ @Override public void startup() { sLock.lock(); try { // Advertise the local-only timer service. if (sLogger.isLoggable(Level.FINER) == true) { sLogger.finer( String.format( "%s: opening and advertising %s feed.", mName, TIMER_KEY)); } // "Compile" the timer messages. DataType.findType(TimerReply.class); DataType.findType(TimerRequest.class); mTimerFeed = EReplyFeed.open( this, TIMER_KEY, FeedScope.LOCAL_ONLY, null); mTimerFeed.advertise(); mTimerFeed.updateFeedState(EFeedState.UP); sStartSignal.signal(); } finally { sLock.unlock(); } return; } // end of startup() /** * Cancels all running timers, stops the timer thread and * retracts the timer service advertisement. */ @Override public void shutdown() { sLock.lock(); try { if (mTimerFeed != null) { if (sLogger.isLoggable(Level.FINER) == true) { sLogger.finer( String.format( "%s: closing %s feed.", mName, TIMER_KEY)); } mTimerFeed.close(); mTimerFeed = null; } synchronized (mTasks) { (mTasks.values()).stream() .forEach((task) -> { task.shutdownTimer(); }); mTasks.clear(); } mTimer.cancel(); sStopSignal.signal(); } finally { sLock.unlock(); } return; } // end of shutdownTimer() // // end of EObject Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // EReplier Interface Implementation. // /** * Starts a timer task based on the given request. If the * timer request is not valid, then sends a generic reply * with a failed status and an explanation for the failure. *

* Do not call this method. This method is * part of the {@link EReplier} interface and is accessed by * the eBus API only. * @param request the eBus request instance. */ @Override public void request(final ERequest request) { final TimerRequest timerReq = (TimerRequest) request.request(); // Need to do this calculation ASAP to minimize chance of // mistakenly declaring a requested time as in the past. final Duration duration = (timerReq.time == null ? Duration.ZERO : Duration.between(Instant.now(), timerReq.time)); ReplyStatus replyStatus = ReplyStatus.OK_FINAL; String reason = null; if (sLogger.isLoggable(Level.FINE) == true) { sLogger.fine( String.format( "%s: received timer request:%n%s", mName, timerReq)); } // Validate the timer request first. // Was an expiration specified? if (timerReq.time == null && timerReq.delay == 0L) { // No. replyStatus = ReplyStatus.ERROR; reason = "must specify either an expiration time or delay"; } // Were both an expiration time and delay specified? else if (timerReq.time != null && timerReq.delay > 0L) { // Yes. replyStatus = ReplyStatus.ERROR; reason = "cannot specify both an expiration time and delay"; } // If the expiration date is given, is it in the past? else if (timerReq.time != null && duration.compareTo(Duration.ZERO) < 0) { // Yes. replyStatus = ReplyStatus.ERROR; reason = "expiration time in the past"; } // If the repeating period is given, is it valid? else if (timerReq.period < 0L) { // Yes. replyStatus = ReplyStatus.ERROR; reason = "period < zero"; } // Otherwise this message is valid. // If the request proved invalid, then send a generic // failure reply now. if (replyStatus == ReplyStatus.ERROR) { final EReplyMessage.ConcreteBuilder builder = (EReplyMessage.ConcreteBuilder) EReplyMessage.builder(); request.reply(builder.subject((request.key()).subject()) .replyStatus(replyStatus) .replyReason(reason) .build()); } // The request checked out. Create and schedule the timer // handler. else { final long delay = (timerReq.time == null ? timerReq.delay : duration.toMillis()); synchronized (mTasks) { mTasks.put(request, startTask(delay, timerReq.period, timerReq.fixedRate, request)); } } return; } // end of request(ERequest) /** * Cancels the timer task associated with this request and * sends a cancel complete reply. *

* Do not call this method. This method is * part of the {@link EReplier} interface and is accessed by * the eBus API only. * @param request timer request being canceled. */ @Override public void cancelRequest(final ERequest request) { // Treat cancel requests and terminations the same. doCancel(request); return; } // end of cancelRequest(ERequest) // // end of EReplier Interface Implementation. //----------------------------------------------------------- /** * Returns {@code true} if the {@code ETimer} instance is * running and {@code false} otherwise. * @return {@code true} if the {@code ETimer} instance is * running. */ public static boolean isETimerRunning() { boolean retcode; sLock.lock(); try { retcode = (sInstance != null); } finally { sLock.unlock(); } return (retcode); } // end of isETimerRunning() /** * Starts the eBus timer service using the default * {@link #DEFAULT_TIMER_NAME timer name} and the timer thread is * not run as a daemon. * @exception IllegalStateException * if the eBus timer service is already running. */ public static void startETimer() throws IllegalStateException { startETimer(DEFAULT_TIMER_NAME, false); return; } // end of startETimer() /** * Starts the eBus timer service using the default * {@link #DEFAULT_TIMER_NAME timer name} and the given daemon * flag. * @param isDaemon {@code true} means that the timer thread * is run as a daemon. * @throws IllegalStateException * if the eBus timer service is already running. */ public static void startETimer(final boolean isDaemon) throws IllegalStateException { startETimer(DEFAULT_TIMER_NAME, isDaemon); return; } // startETiimer(boolean) /** * Starts the eBus timer service for the given timer thread * name. The timer thread is not run as a daemon. * @param name the timer thread name. * @throws IllegalArgumentException * if {@code name} is either {@code null} or empty. * @throws IllegalStateException * if the eBus timer service is already running. */ public static void startETimer(final String name) throws IllegalArgumentException, IllegalStateException { startETimer(name, false); return; } // end of startETimer(String) /** * Starts the eBus timer service for the given timer thread * name and daemon flag. * @param name the timer thread name. * @param isDaemon if {@code true}, then the timer is a * daemon thread. * @throws IllegalArgumentException * if {@code name} is either {@code null} or empty. * @throws IllegalStateException * if the eBus timer service is already running. */ public static void startETimer(final String name, final boolean isDaemon) throws IllegalArgumentException, IllegalStateException { if (name == null || name.isEmpty() == true) { throw ( new IllegalArgumentException( "null or empty name")); } sLock.lock(); try { if (sInstance != null) { throw ( new IllegalStateException( "ETimer already started")); } else { sLogger.fine( String.format( "Creating ETimer %s (%s)", name, (isDaemon == true ? "daemon" : "not daemon"))); // Create the singleton and have it do advertise // its service. sInstance = new ETimer(name, isDaemon); EFeed.register(sInstance); EFeed.startup(sInstance); // Wait here for the entry to start up to // complete. sLogger.finest( String.format( "Waiting for ETimer %s start-up to complete.", name)); try { sStartSignal.await(); } catch (InterruptedException interrupt) {} sLogger.finest( String.format( "ETimer %s start-up completed.", name)); } } finally { sLock.unlock(); } return; } // end of startETimer(String, boolean) /** * Stops the eBus timer service, if running. All running * timer requests are canceled and requestors are informed. *

* Does nothing if the timer service is not running. */ public static void stopETimer() { sLock.lock(); try { if (sInstance != null) { EFeed.shutdown(sInstance); // Wait here for the entry to start up to // complete. sLogger.finest( "Waiting for ETimer shutdown to complete."); try { sStopSignal.await(); } catch (InterruptedException interrupt) {} sLogger.finest("ETimer shutdown completed."); sInstance = null; sLogger.fine("Deleted ETimer singleton."); } } finally { sLock.unlock(); } return; } // end of stopETimer() /** * Performs the actual work of canceling a timer request. * @param request cancel this request. */ private void doCancel(final ERequest request) { final TimerHandler task; if (sLogger.isLoggable(Level.FINE) == true) { sLogger.fine( String.format( "%s: canceling timer request ", mName, request.feedId())); } // Find the timer task associated with the request and // and stop it. synchronized (mTasks) { task = mTasks.remove(request); } if (task != null) { task.cancelTimer(); } return; } // end of doCancel(ERequest) /** * Creates the timer task which posts the timer reply, * schedules the timer, and then returns it. * @param delay initial timer delay. * @param period repeating timer period. * @param isFixedRate {@code true} if this is a fixed rate * timer. * @param request the eBus request instance associated with * the message. * @return the timer handler instance. */ private TimerHandler startTask(final long delay, final long period, final boolean isFixedRate, final ERequest request) { final TimerHandler retval; // Is this a repeating timer? if (period > 0L) { // Yes. Is this a fixed rate timer? retval = new TimerHandler(request, this, false); if (isFixedRate == true) { mTimer.scheduleAtFixedRate( retval, delay, period); } // No, this is not fixed rate. else { mTimer.schedule(retval, delay, period); } } else { // No, this is a single shot timer. retval = new TimerHandler(request, this, true); mTimer.schedule(retval, delay); } return (retval); } // end of startTask(TimerRequest, ERequest) /** * Removes the completed timer task from the map. * @param request the associated timer request. */ private void taskDone(final ERequest request) { synchronized (mTasks) { mTasks.remove(request); } return; } // end of taskDone(ERequest) //--------------------------------------------------------------- // Inner classes. // /** * A timer handler instance is created for each accepted * {@link TimerRequest}. This class is responsible for * sending {@link TimerReply} messages when the timer task * is executed. */ private static final class TimerHandler extends TimerTask { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Send replies via this eBus request. */ private final ERequest mRequest; /** * This handler works for this eBus timer service. */ private final ETimer mTimer; /** * {@code true} if this is a one shot timer and * {@code false} if on-going. */ private final boolean mOneTimeFlag; /** * Count up the number of timer expirations. */ private int mCount; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new timer request instance for handling * the given eBus request. * @param request the eBus request. Post replies to this * object. * @param timer the timer instance registered with the * eBus request. * @param oneTimeFlag {@code true} if this is a one shot * timer. */ private TimerHandler(final ERequest request, final ETimer timer, final boolean oneTimeFlag) { mRequest = request; mTimer = timer; mOneTimeFlag = oneTimeFlag; mCount = 0; } // end of TimerHandler(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // TimerTask Interface Implementation. // /** * Sends the timer reply message. If this is a one shot * timer, removes this timer handler from the tasks map. */ @Override public void run() { if (mRequest.isActive() == true) { final ReplyStatus replyStatus = (mOneTimeFlag == true ? ReplyStatus.OK_FINAL : ReplyStatus.OK_CONTINUING); synchronized (this) { // Send the timer expiration reply to // requestor. If this is a single shot timer, // then this is the first and final reply, so // remove this timer from the map. mRequest.reply( TimerReply.builder() .timerName((mRequest.key()).subject()) .replyStatus(replyStatus) .sequenceNumber(mCount) .build()); ++mCount; } } if (mOneTimeFlag == true) { mTimer.taskDone(mRequest); } return; } // end of handleTimeout() // // end of TimerTaskListener Interface Implementation. //------------------------------------------------------- /** * Cancels the timer task and sends a cancel reply. */ public void cancelTimer() { this.cancel(); return; } // end of void cancelTimer() /** * Cancels the timer task and informs the requestor that * the task failed due to the timer service being shut * down. */ public void shutdownTimer() { this.cancel(); if (mRequest.isActive() == true) { final EReplyMessage.ConcreteBuilder builder = (EReplyMessage.ConcreteBuilder) EReplyMessage.builder(); mRequest.reply( builder.subject((mRequest.key()).subject()) .replyStatus(ReplyStatus.ERROR) .replyReason("timer service shutdown") .build()); } return; } // end of shutdownTimer() } // end of class TimerHandler } // end of class ETimer





© 2015 - 2025 Weber Informatics LLC | Privacy Policy