001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.mx.timer;
023:
024: import java.util.Date;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.Vector;
028:
029: import javax.management.InstanceNotFoundException;
030: import javax.management.MBeanRegistration;
031: import javax.management.MBeanServer;
032: import javax.management.NotificationBroadcasterSupport;
033: import javax.management.ObjectName;
034: import javax.management.timer.TimerNotification;
035:
036: import org.jboss.logging.Logger;
037: import org.jboss.mx.util.RunnableScheduler;
038: import org.jboss.mx.util.SchedulableRunnable;
039:
040: /**
041: * A clone of the JBossMX javax.management.timer.Timer service.
042: *
043: * There are indications that the jdk5 javax.management.timer.Timer
044: * uses internally a single-threaded implementation for executing
045: * scheduled tasks, so scheduling of multiple tasks is affected
046: * when moving from jdk1.4 and the jboss implementation of Timer,
047: * to a jdk5 runtime.
048: *
049: * The JBossMX Timer implementation in contrast uses a dynamically
050: * extensible thread pool to execute scheduled tasks. Since we don't
051: * control the jdk5 implementation, we've cloned the jboss timer
052: * so it can be used as a drop-in replacement of the jdk JMX Timer.
053: *
054: * The two classes *should* be kept in sync, or instead change our
055: * javax.management.timer.Timer to delegate to this class.
056: *
057: * @see javax.management.timer.Timer
058: *
059: * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>
060: * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
061: * @version $Revision: 57200 $
062: */
063: public class JBossTimer extends NotificationBroadcasterSupport
064: implements JBossTimerMBean, MBeanRegistration {
065: // logging support
066: private static Logger log = Logger.getLogger(JBossTimer.class);
067:
068: // Constants -----------------------------------------------------
069:
070: /** The number of milliseconds in one second. */
071: public static final long ONE_SECOND = 1000;
072:
073: /** The number of milliseconds in one minute. */
074: public static final long ONE_MINUTE = ONE_SECOND * 60;
075:
076: /** The number of milliseconds in one hour. */
077: public static final long ONE_HOUR = ONE_MINUTE * 60;
078:
079: /** The number of milliseconds in one day. */
080: public static final long ONE_DAY = ONE_HOUR * 24;
081:
082: /** The number of milliseconds in one week. */
083: public static final long ONE_WEEK = ONE_DAY * 7;
084:
085: /** Don't send notifications at initial start up. */
086: private static final int SEND_NO = 0;
087:
088: /** Send all past notifications at initial start up. */
089: private static final int SEND_START = 1;
090:
091: /** Normal operation sending */
092: private static final int SEND_NORMAL = 2;
093:
094: // Attributes ----------------------------------------------------
095:
096: /** The next notification id. */
097: int nextId = 0;
098:
099: /** The next notification sequence number. */
100: long sequenceNumber = 0;
101:
102: /** The send past events attribute. */
103: boolean sendPastNotifications = false;
104:
105: /** Whether the service is active. */
106: boolean active = false;
107:
108: /** Our object name. */
109: ObjectName objectName;
110:
111: /** The registered notifications. */
112: HashMap notifications = new HashMap();
113:
114: /** The scheduler */
115: private RunnableScheduler scheduler = new RunnableScheduler();
116:
117: // Static --------------------------------------------------------
118:
119: // Constructors --------------------------------------------------
120:
121: // Public --------------------------------------------------------
122:
123: // TimerMBean implementation -------------------------------------
124:
125: public Integer addNotification(String type, String message,
126: Object userData, Date date) throws IllegalArgumentException {
127: return addNotification(type, message, userData, date, 0);
128: }
129:
130: public Integer addNotification(String type, String message,
131: Object userData, Date date, long period)
132: throws IllegalArgumentException {
133: return addNotification(type, message, userData, date, period, 0);
134: }
135:
136: public Integer addNotification(String type, String message,
137: Object userData, Date date, long period, long occurences)
138: throws IllegalArgumentException {
139: return addNotification(type, message, userData, date, period,
140: occurences, false);
141: }
142:
143: /**
144: * 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.
145: * <p/>
146: * 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.
147: * For once-off notifications, the notification is delivered immediately.
148: * For periodic notifications, the first notification is delivered immediately and the subsequent ones are spaced as specified by the period parameter.
149: * <p/>
150: * 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.
151: * <p/>
152: * In the case of a periodic notification, the value of parameter fixedRate is used to specify the execution scheme, as specified in Timer.
153: *
154: * @param type The timer notification type.
155: * @param message The timer notification detailed message.
156: * @param userData The timer notification user data object.
157: * @param date The date when the notification occurs.
158: * @param period The period of the timer notification (in milliseconds).
159: * @param nbOccurences The total number the timer notification will be emitted.
160: * @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.
161: * @return The identifier of the new created timer notification.
162: * @throws IllegalArgumentException The period or the number of occurences is negative
163: */
164: public Integer addNotification(String type, String message,
165: Object userData, Date date, long period, long nbOccurences,
166: boolean fixedRate) throws IllegalArgumentException {
167: // Generate the next id.
168: int newId = 0;
169: newId = ++nextId;
170: Integer id = new Integer(newId);
171:
172: // Validate and create the registration.
173: RegisteredNotification rn = new RegisteredNotification(id,
174: type, message, userData, date, period, nbOccurences,
175: fixedRate);
176:
177: // Add the registration.
178: synchronized (notifications) {
179: notifications.put(id, rn);
180: rn.setNextRun(rn.nextDate);
181: rn.setScheduler(scheduler);
182: }
183:
184: return id;
185: }
186:
187: public Vector getAllNotificationIDs() {
188: synchronized (notifications) {
189: return new Vector(notifications.keySet());
190: }
191: }
192:
193: public Date getDate(Integer id) {
194: // Make sure there is a registration
195: RegisteredNotification rn = (RegisteredNotification) notifications
196: .get(id);
197: if (rn == null)
198: return null;
199:
200: // Return a copy of the date.
201: return new Date(rn.startDate);
202: }
203:
204: public int getNbNotifications() {
205: return notifications.size();
206: }
207:
208: public Long getNbOccurences(Integer id) {
209: // Make sure there is a registration
210: RegisteredNotification rn = (RegisteredNotification) notifications
211: .get(id);
212: if (rn == null)
213: return null;
214:
215: // Return a copy of the occurences.
216: return new Long(rn.occurences);
217: }
218:
219: /**
220: * Gets a copy of the flag indicating whether a peridic notification is executed at fixed-delay or at fixed-rate.
221: *
222: * @param id The timer notification identifier.
223: * @return A copy of the flag indicating whether a peridic notification is executed at fixed-delay or at fixed-rate.
224: */
225: public Boolean getFixedRate(Integer id) {
226: // Make sure there is a registration
227: RegisteredNotification rn = (RegisteredNotification) notifications
228: .get(id);
229: if (rn == null)
230: return null;
231:
232: // Return a copy of the fixedRate
233: return new Boolean(rn.fixedRate);
234: }
235:
236: public Vector getNotificationIDs(String type) {
237: Vector result = new Vector();
238:
239: // Loop through the notifications looking for the passed type.
240: synchronized (notifications) {
241: Iterator iterator = notifications.values().iterator();
242: while (iterator.hasNext()) {
243: RegisteredNotification rn = (RegisteredNotification) iterator
244: .next();
245: if (rn.type.equals(type))
246: result.add(rn.id);
247: }
248: }
249:
250: return result;
251: }
252:
253: public String getNotificationMessage(Integer id) {
254: // Make sure there is a registration
255: RegisteredNotification rn = (RegisteredNotification) notifications
256: .get(id);
257: if (rn == null)
258: return null;
259:
260: // Return the message
261: return rn.message;
262: }
263:
264: public String getNotificationType(Integer id) {
265: // Make sure there is a registration
266: RegisteredNotification rn = (RegisteredNotification) notifications
267: .get(id);
268: if (rn == null)
269: return null;
270:
271: // Return the type.
272: return rn.type;
273: }
274:
275: public Object getNotificationUserData(Integer id) {
276: // Make sure there is a registration
277: RegisteredNotification rn = (RegisteredNotification) notifications
278: .get(id);
279: if (rn == null)
280: return null;
281:
282: // Return the user data.
283: return rn.userData;
284: }
285:
286: public Long getPeriod(Integer id) {
287: // Make sure there is a registration
288: RegisteredNotification rn = (RegisteredNotification) notifications
289: .get(id);
290: if (rn == null)
291: return null;
292:
293: // Return a copy of the period
294: return new Long(rn.period);
295: }
296:
297: public boolean getSendPastNotifications() {
298: return sendPastNotifications;
299: }
300:
301: public boolean isActive() {
302: return active;
303: }
304:
305: public boolean isEmpty() {
306: return notifications.isEmpty();
307: }
308:
309: public void removeAllNotifications() {
310: // Remove the notifications
311: synchronized (notifications) {
312: Iterator iterator = notifications.values().iterator();
313: while (iterator.hasNext()) {
314: RegisteredNotification rn = (RegisteredNotification) iterator
315: .next();
316: rn.setScheduler(null);
317: iterator.remove();
318: }
319: }
320:
321: // The spec says to reset the identifiers, seems like a bad idea to me
322: synchronized (this ) {
323: nextId = 0;
324: }
325: }
326:
327: public void removeNotification(Integer id)
328: throws InstanceNotFoundException {
329:
330: log.debug("removeNotification: " + objectName + ",id=" + id);
331:
332: // Check if there is a notification.
333: synchronized (notifications) {
334: RegisteredNotification rn = (RegisteredNotification) notifications
335: .get(id);
336: if (rn == null)
337: throw new InstanceNotFoundException(
338: "No notification id : " + id.toString());
339:
340: // Remove the notification
341: rn.setScheduler(null);
342: notifications.remove(id);
343: }
344: }
345:
346: public void removeNotifications(String type)
347: throws InstanceNotFoundException {
348: boolean found = false;
349:
350: log.debug("removeNotifications: " + objectName + ",type="
351: + type);
352:
353: // Loop through the notifications removing the passed type.
354: synchronized (notifications) {
355: Iterator iterator = notifications.values().iterator();
356: while (iterator.hasNext()) {
357: RegisteredNotification rn = (RegisteredNotification) iterator
358: .next();
359: if (rn.type.equals(type)) {
360: rn.setScheduler(null);
361: iterator.remove();
362: found = true;
363: }
364: }
365: }
366:
367: // The spec says to through an exception when nothing removed.
368: if (found == false)
369: throw new InstanceNotFoundException(
370: "Nothing registered for type: " + type);
371: }
372:
373: public void setSendPastNotifications(boolean value) {
374: log.debug("setSendPastNotifications: " + objectName + ",value="
375: + value);
376: sendPastNotifications = value;
377: }
378:
379: public synchronized void start() {
380: // Ignore if already active
381: if (active == true)
382: return;
383: active = true;
384:
385: log.debug("start: " + objectName + " at " + new Date());
386:
387: // Perform the initial sends, for past notifications send missed events
388: // otherwise ignore them
389: synchronized (notifications) {
390: Iterator iterator = notifications.values().iterator();
391: while (iterator.hasNext()) {
392: RegisteredNotification rn = (RegisteredNotification) iterator
393: .next();
394: if (sendPastNotifications)
395: rn.sendType = SEND_START;
396: else
397: rn.sendType = SEND_NO;
398: sendNotifications(rn);
399: rn.sendType = SEND_NORMAL;
400: }
401: }
402:
403: // Start 'em up
404: scheduler.start();
405: }
406:
407: public synchronized void stop() {
408: // Ignore if not active
409: if (active == false)
410: return;
411:
412: log.debug("stop: " + objectName + ",now=" + new Date());
413:
414: // Stop the threads
415: active = false;
416: scheduler.stop();
417: }
418:
419: // MBeanRegistrationImplementation overrides ---------------------
420:
421: public ObjectName preRegister(MBeanServer server,
422: ObjectName objectName) throws Exception {
423: // Save the object name
424: this .objectName = objectName;
425:
426: // Use the passed object name.
427: return objectName;
428: }
429:
430: public void postRegister(Boolean registrationDone) {
431: }
432:
433: public void preDeregister() throws Exception {
434: // Stop the timer before deregistration.
435: stop();
436: }
437:
438: public void postDeregister() {
439: }
440:
441: // Package protected ---------------------------------------------
442:
443: // Protected -----------------------------------------------------
444:
445: // Private -------------------------------------------------------
446:
447: /**
448: * Send any outstanding notifications.
449: *
450: * @param rn the registered notification to send.
451: */
452: private void sendNotifications(RegisteredNotification rn) {
453: // Keep going until we have done all outstanding notifications.
454: // The loop ends when not active, or there are no outstanding
455: // notifications.
456: // REVIEW: In practice for normal operation it never loops. We
457: // ignore sends that we have missed. This avoids problems where
458: // the notification takes longer than the period. Correct???
459: while (isActive() && rn.nextDate != 0
460: && rn.nextDate <= System.currentTimeMillis()) {
461: // Do we actually send it?
462: // Yes, unless start and not sending past notifications.
463: if (rn.sendType != SEND_NO) {
464: long seq = 0;
465: synchronized (this ) {
466: seq = ++sequenceNumber;
467: }
468:
469: log.debug("sendNotification: " + rn);
470: TimerNotification tn = new TimerNotification(rn.type,
471: objectName, seq, rn.nextDate, rn.message, rn.id);
472: tn.setUserData(rn.userData);
473: sendNotification(tn);
474: }
475: // Calculate the next date.
476: // Except for when we are sending past notifications at start up,
477: // it cannot be in the future.
478: do {
479: // If no next run, remove it sets the next date to zero.
480: if (rn.calcNextDate() == false) {
481: synchronized (notifications) {
482: log.debug("remove: " + rn);
483: notifications.remove(rn.id);
484: }
485: }
486: } while (isActive() && rn.sendType != SEND_START
487: && rn.nextDate != 0 && rn.occurences == 0
488: && rn.nextDate < System.currentTimeMillis());
489: }
490:
491: if (rn.nextDate != 0)
492: rn.setNextRun(rn.nextDate);
493: }
494:
495: // Inner classes -------------------------------------------------
496:
497: /**
498: * A registered notification. These run as separate threads.
499: */
500: private class RegisteredNotification extends SchedulableRunnable {
501: // Attributes ----------------------------------------------------
502:
503: /** The notification id. */
504: public Integer id;
505:
506: /** The notification type. */
507: public String type;
508:
509: /** The message. */
510: public String message;
511:
512: /** The user data. */
513: public Object userData;
514:
515: /** The start date. */
516: public long startDate;
517:
518: /** The period. */
519: public long period;
520:
521: /** The maximum number of occurences. */
522: public long occurences;
523:
524: /** The flag to indicate fixedRate notifications, or fixedDelay (default) */
525: public boolean fixedRate;
526:
527: /** The send type, no send, past notifications or normal */
528: public int sendType = SEND_NORMAL;
529:
530: /** The next run date */
531: public long nextDate = 0;
532:
533: // Constructors --------------------------------------------------
534:
535: /**
536: * The default constructor.
537: *
538: * @param id the notification id.
539: * @param type the notification type.
540: * @param message the notification's message string.
541: * @param userData the notification's user data.
542: * @param startDate the date/time the notification will occur.
543: * @param period the repeat period in milli-seconds. Passing zero means
544: * no repeat.
545: * @param occurences the maximum number of repeats. When the period is not
546: * zero and this parameter is zero, it will repeat indefinitely.
547: * @param fixedRate If true and if the notification is periodic, the notification
548: * is scheduled with a fixed-rate execution scheme. If false and if the notification
549: * is periodic, the notification is scheduled with a fixed-delay execution scheme.
550: * Ignored if the notification is not periodic.
551: *
552: * @exception IllegalArgumentException when the date is before the current
553: * date, the period is negative or the number of repeats is
554: * negative.
555: */
556: public RegisteredNotification(Integer id, String type,
557: String message, Object userData, Date startDate,
558: long period, long occurences, boolean fixedRate)
559: throws IllegalArgumentException {
560: // Basic validation
561: if (startDate == null)
562: throw new IllegalArgumentException("Null Date");
563: if (period < 0)
564: throw new IllegalArgumentException("Negative Period");
565: if (occurences < 0)
566: throw new IllegalArgumentException(
567: "Negative Occurences");
568:
569: this .startDate = startDate.getTime();
570: if (startDate.getTime() < System.currentTimeMillis()) {
571: log.debug("startDate [" + startDate
572: + "] in the past, set to now");
573: this .startDate = System.currentTimeMillis();
574: }
575:
576: // Remember the values
577: this .id = id;
578: this .type = type;
579: this .message = message;
580: this .userData = userData;
581: this .period = period;
582: this .occurences = occurences;
583: this .fixedRate = fixedRate;
584:
585: this .nextDate = this .startDate;
586:
587: String msgStr = "new " + this .toString();
588: log.debug(msgStr);
589: }
590:
591: // Public --------------------------------------------------------
592:
593: /**
594: * Calculate the next notification date. Add on the period until
595: * the number of occurences is exhausted.
596: *
597: * @return false when there are no more occurences, true otherwise.
598: */
599: boolean calcNextDate() {
600: // No period, we've finished
601: if (period == 0) {
602: nextDate = 0;
603: return false;
604: }
605:
606: // Limited number of repeats have we finished?
607: if (occurences != 0 && --occurences == 0) {
608: nextDate = 0;
609: return false;
610: }
611:
612: // Calculate the next occurence
613: if (fixedRate)
614: nextDate += period;
615: else
616: // fixed delay
617: nextDate = System.currentTimeMillis() + period;
618:
619: return true;
620: }
621:
622: // SchedulableRunnable overrides ---------------------------------
623:
624: /**
625: * Send the notifications.
626: */
627: public void doRun() {
628: // Send any notifications
629: sendNotifications(this );
630: }
631:
632: public String toString() {
633: return " RegisteredNotification: [timer=" + objectName
634: + ",id=" + id + ",startDate=" + new Date(startDate)
635: + ",period=" + period + ",occurences=" + occurences
636: + ",fixedRate=" + fixedRate + ",nextDate="
637: + new Date(nextDate) + "]";
638: }
639: }
640: }
|