/* * JBoss, Home of Professional Open Source. * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This 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 software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.mx.timer; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Vector; import javax.management.InstanceNotFoundException; import javax.management.MBeanRegistration; import javax.management.MBeanServer; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; import javax.management.timer.TimerNotification; import org.jboss.logging.Logger; import org.jboss.mx.util.RunnableScheduler; import org.jboss.mx.util.SchedulableRunnable; /** * A clone of the JBossMX javax.management.timer.Timer service. * * There are indications that the jdk5 javax.management.timer.Timer * uses internally a single-threaded implementation for executing * scheduled tasks, so scheduling of multiple tasks is affected * when moving from jdk1.4 and the jboss implementation of Timer, * to a jdk5 runtime. * * The JBossMX Timer implementation in contrast uses a dynamically * extensible thread pool to execute scheduled tasks. Since we don't * control the jdk5 implementation, we've cloned the jboss timer * so it can be used as a drop-in replacement of the jdk JMX Timer. * * The two classes *should* be kept in sync, or instead change our * javax.management.timer.Timer to delegate to this class. * * @see javax.management.timer.Timer * * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a> * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a> * @version $Revision: 85945 $ */ public class JBossTimer extends NotificationBroadcasterSupport implements JBossTimerMBean, MBeanRegistration { // logging support private static Logger log = Logger.getLogger(JBossTimer.class); // Constants ----------------------------------------------------- /** The number of milliseconds in one second. */ public static final long ONE_SECOND = 1000; /** The number of milliseconds in one minute. */ public static final long ONE_MINUTE = ONE_SECOND * 60; /** The number of milliseconds in one hour. */ public static final long ONE_HOUR = ONE_MINUTE * 60; /** The number of milliseconds in one day. */ public static final long ONE_DAY = ONE_HOUR * 24; /** The number of milliseconds in one week. */ public static final long ONE_WEEK = ONE_DAY * 7; /** Don't send notifications at initial start up. */ private static final int SEND_NO = 0; /** Send all past notifications at initial start up. */ private static final int SEND_START = 1; /** Normal operation sending */ private static final int SEND_NORMAL = 2; // Attributes ---------------------------------------------------- /** The next notification id. */ int nextId = 0; /** The next notification sequence number. */ long sequenceNumber = 0; /** The send past events attribute. */ boolean sendPastNotifications = false; /** Whether the service is active. */ boolean active = false; /** Our object name. */ ObjectName objectName; /** The registered notifications. */ HashMap notifications = new HashMap(); /** The scheduler */ private RunnableScheduler scheduler = new RunnableScheduler(); // Static -------------------------------------------------------- // Constructors -------------------------------------------------- // Public -------------------------------------------------------- // TimerMBean implementation ------------------------------------- public Integer addNotification(String type, String message, Object userData, Date date) throws IllegalArgumentException { return addNotification(type, message, userData, date, 0); } public Integer addNotification(String type, String message, Object userData, Date date, long period) throws IllegalArgumentException { return addNotification(type, message, userData, date, period, 0); } public Integer addNotification(String type, String message, Object userData, Date date, long period, long occurences) throws IllegalArgumentException { return addNotification(type, message, userData, date, period, occurences, false); } /** * Creates a new timer notification with the specified type, message and userData and inserts it into the list of notifications with a given date, period and number of occurences. * <p/> * If the timer notification to be inserted has a date that is before the current date, the method behaves as if the specified date were the current date. * For once-off notifications, the notification is delivered immediately. * For periodic notifications, the first notification is delivered immediately and the subsequent ones are spaced as specified by the period parameter. * <p/> * Note that once the timer notification has been added into the list of notifications, its associated date, period and number of occurences cannot be updated. * <p/> * In the case of a periodic notification, the value of parameter fixedRate is used to specify the execution scheme, as specified in Timer. * * @param type The timer notification type. * @param message The timer notification detailed message. * @param userData The timer notification user data object. * @param date The date when the notification occurs. * @param period The period of the timer notification (in milliseconds). * @param nbOccurences The total number the timer notification will be emitted. * @param fixedRate If true and if the notification is periodic, the notification is scheduled with a fixed-rate execution scheme. If false and if the notification is periodic, the notification is scheduled with a fixed-delay execution scheme. Ignored if the notification is not periodic. * @return The identifier of the new created timer notification. * @throws IllegalArgumentException The period or the number of occurences is negative */ public Integer addNotification(String type, String message, Object userData, Date date, long period, long nbOccurences, boolean fixedRate) throws IllegalArgumentException { // Generate the next id. int newId = 0; newId = ++nextId; Integer id = new Integer(newId); // Validate and create the registration. RegisteredNotification rn = new RegisteredNotification(id, type, message, userData, date, period, nbOccurences, fixedRate); // Add the registration. synchronized(notifications) { notifications.put(id, rn); rn.setNextRun(rn.nextDate); rn.setScheduler(scheduler); } return id; } public Vector getAllNotificationIDs() { synchronized(notifications) { return new Vector(notifications.keySet()); } } public Date getDate(Integer id) { // Make sure there is a registration RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) return null; // Return a copy of the date. return new Date(rn.startDate); } public int getNbNotifications() { return notifications.size(); } public Long getNbOccurences(Integer id) { // Make sure there is a registration RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) return null; // Return a copy of the occurences. return new Long(rn.occurences); } /** * Gets a copy of the flag indicating whether a peridic notification is executed at fixed-delay or at fixed-rate. * * @param id The timer notification identifier. * @return A copy of the flag indicating whether a peridic notification is executed at fixed-delay or at fixed-rate. */ public Boolean getFixedRate(Integer id) { // Make sure there is a registration RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) return null; // Return a copy of the fixedRate return new Boolean(rn.fixedRate); } public Vector getNotificationIDs(String type) { Vector result = new Vector(); // Loop through the notifications looking for the passed type. synchronized (notifications) { Iterator iterator = notifications.values().iterator(); while (iterator.hasNext()) { RegisteredNotification rn = (RegisteredNotification) iterator.next(); if (rn.type.equals(type)) result.add(rn.id); } } return result; } public String getNotificationMessage(Integer id) { // Make sure there is a registration RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) return null; // Return the message return rn.message; } public String getNotificationType(Integer id) { // Make sure there is a registration RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) return null; // Return the type. return rn.type; } public Object getNotificationUserData(Integer id) { // Make sure there is a registration RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) return null; // Return the user data. return rn.userData; } public Long getPeriod(Integer id) { // Make sure there is a registration RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) return null; // Return a copy of the period return new Long(rn.period); } public boolean getSendPastNotifications() { return sendPastNotifications; } public boolean isActive() { return active; } public boolean isEmpty() { return notifications.isEmpty(); } public void removeAllNotifications() { // Remove the notifications synchronized(notifications) { Iterator iterator = notifications.values().iterator(); while (iterator.hasNext()) { RegisteredNotification rn = (RegisteredNotification) iterator.next(); rn.setScheduler(null); iterator.remove(); } } // The spec says to reset the identifiers, seems like a bad idea to me synchronized (this) { nextId = 0; } } public void removeNotification(Integer id) throws InstanceNotFoundException { log.debug("removeNotification: " + objectName + ",id=" + id); // Check if there is a notification. synchronized(notifications) { RegisteredNotification rn = (RegisteredNotification) notifications.get(id); if (rn == null) throw new InstanceNotFoundException("No notification id : " + id.toString()); // Remove the notification rn.setScheduler(null); notifications.remove(id); } } public void removeNotifications(String type) throws InstanceNotFoundException { boolean found = false; log.debug("removeNotifications: " + objectName + ",type=" + type); // Loop through the notifications removing the passed type. synchronized(notifications) { Iterator iterator = notifications.values().iterator(); while (iterator.hasNext()) { RegisteredNotification rn = (RegisteredNotification) iterator.next(); if (rn.type.equals(type)) { rn.setScheduler(null); iterator.remove(); found = true; } } } // The spec says to through an exception when nothing removed. if (found == false) throw new InstanceNotFoundException("Nothing registered for type: " + type); } public void setSendPastNotifications(boolean value) { log.debug("setSendPastNotifications: " + objectName + ",value=" + value); sendPastNotifications = value; } public synchronized void start() { // Ignore if already active if (active == true) return; active = true; log.debug("start: " + objectName + " at " + new Date()); // Perform the initial sends, for past notifications send missed events // otherwise ignore them synchronized (notifications) { Iterator iterator = notifications.values().iterator(); while (iterator.hasNext()) { RegisteredNotification rn = (RegisteredNotification) iterator.next(); if (sendPastNotifications) rn.sendType = SEND_START; else rn.sendType = SEND_NO; sendNotifications(rn); rn.sendType = SEND_NORMAL; } } // Start 'em up scheduler.start(); } public synchronized void stop() { // Ignore if not active if (active == false) return; log.debug("stop: " + objectName + ",now=" + new Date()); // Stop the threads active = false; scheduler.stop(); } // MBeanRegistrationImplementation overrides --------------------- public ObjectName preRegister(MBeanServer server, ObjectName objectName) throws Exception { // Save the object name this.objectName = objectName; // Use the passed object name. return objectName; } public void postRegister(Boolean registrationDone) { } public void preDeregister() throws Exception { // Stop the timer before deregistration. stop(); } public void postDeregister() { } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- /** * Send any outstanding notifications. * * @param rn the registered notification to send. */ private void sendNotifications(RegisteredNotification rn) { // Keep going until we have done all outstanding notifications. // The loop ends when not active, or there are no outstanding // notifications. // REVIEW: In practice for normal operation it never loops. We // ignore sends that we have missed. This avoids problems where // the notification takes longer than the period. Correct??? while (isActive() && rn.nextDate != 0 && rn.nextDate <= System.currentTimeMillis()) { // Do we actually send it? // Yes, unless start and not sending past notifications. if (rn.sendType != SEND_NO) { long seq = 0; synchronized (this) { seq = ++sequenceNumber; } log.debug("sendNotification: " + rn); TimerNotification tn = new TimerNotification(rn.type, objectName, seq, rn.nextDate, rn.message, rn.id); tn.setUserData(rn.userData); sendNotification(tn); } // Calculate the next date. // Except for when we are sending past notifications at start up, // it cannot be in the future. do { // If no next run, remove it sets the next date to zero. if (rn.calcNextDate() == false) { synchronized (notifications) { log.debug("remove: " + rn); notifications.remove(rn.id); } } } while (isActive() && rn.sendType != SEND_START && rn.nextDate != 0 && rn.occurences == 0 && rn.nextDate < System.currentTimeMillis()); } if (rn.nextDate != 0) rn.setNextRun(rn.nextDate); } // Inner classes ------------------------------------------------- /** * A registered notification. These run as separate threads. */ private class RegisteredNotification extends SchedulableRunnable { // Attributes ---------------------------------------------------- /** The notification id. */ public Integer id; /** The notification type. */ public String type; /** The message. */ public String message; /** The user data. */ public Object userData; /** The start date. */ public long startDate; /** The period. */ public long period; /** The maximum number of occurences. */ public long occurences; /** The flag to indicate fixedRate notifications, or fixedDelay (default) */ public boolean fixedRate; /** The send type, no send, past notifications or normal */ public int sendType = SEND_NORMAL; /** The next run date */ public long nextDate = 0; // Constructors -------------------------------------------------- /** * The default constructor. * * @param id the notification id. * @param type the notification type. * @param message the notification's message string. * @param userData the notification's user data. * @param startDate the date/time the notification will occur. * @param period the repeat period in milli-seconds. Passing zero means * no repeat. * @param occurences the maximum number of repeats. When the period is not * zero and this parameter is zero, it will repeat indefinitely. * @param fixedRate If true and if the notification is periodic, the notification * is scheduled with a fixed-rate execution scheme. If false and if the notification * is periodic, the notification is scheduled with a fixed-delay execution scheme. * Ignored if the notification is not periodic. * * @exception IllegalArgumentException when the date is before the current * date, the period is negative or the number of repeats is * negative. */ public RegisteredNotification(Integer id, String type, String message, Object userData, Date startDate, long period, long occurences, boolean fixedRate) throws IllegalArgumentException { // Basic validation if (startDate == null) throw new IllegalArgumentException("Null Date"); if (period < 0) throw new IllegalArgumentException("Negative Period"); if (occurences < 0) throw new IllegalArgumentException("Negative Occurences"); this.startDate = startDate.getTime(); if (startDate.getTime() < System.currentTimeMillis()) { log.debug("startDate [" + startDate + "] in the past, set to now"); this.startDate = System.currentTimeMillis(); } // Remember the values this.id = id; this.type = type; this.message = message; this.userData = userData; this.period = period; this.occurences = occurences; this.fixedRate = fixedRate; this.nextDate = this.startDate; String msgStr = "new " + this.toString(); log.debug(msgStr); } // Public -------------------------------------------------------- /** * Calculate the next notification date. Add on the period until * the number of occurences is exhausted. * * @return false when there are no more occurences, true otherwise. */ boolean calcNextDate() { // No period, we've finished if (period == 0) { nextDate = 0; return false; } // Limited number of repeats have we finished? if (occurences != 0 && --occurences == 0) { nextDate = 0; return false; } // Calculate the next occurence if (fixedRate) nextDate += period; else // fixed delay nextDate = System.currentTimeMillis() + period; return true; } // SchedulableRunnable overrides --------------------------------- /** * Send the notifications. */ public void doRun() { // Send any notifications sendNotifications(this); } public String toString() { return " RegisteredNotification: [timer=" + objectName + ",id=" + id + ",startDate=" + new Date(startDate) + ",period=" + period + ",occurences=" + occurences + ",fixedRate=" + fixedRate + ",nextDate=" + new Date(nextDate) + "]"; } } }