Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/mobicents/slee/runtime/facilities/TimerFacilityImpl.java


1   /***************************************************
2    *                                                 *
3    *  Mobicents: The Open Source VoIP Platform       *
4    *                                                 *
5    *  Distributable under LGPL license.              *
6    *  See terms of license at gnu.org.               *
7    *                                                 *
8    ***************************************************/
9   
10  package org.mobicents.slee.runtime.facilities;
11  
12  
13  import java.io.Serializable;
14  import java.util.Date;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.Timer;
18  import java.util.TimerTask;
19  
20  import javax.slee.ActivityContextInterface;
21  import javax.slee.Address;
22  import javax.slee.EventTypeID;
23  import javax.slee.InvalidStateException;
24  import javax.slee.TransactionRolledbackLocalException;
25  import javax.slee.facilities.FacilityException;
26  import javax.slee.facilities.TimerEvent;
27  import javax.slee.facilities.TimerFacility;
28  import javax.slee.facilities.TimerID;
29  import javax.slee.facilities.TimerOptions;
30  import javax.slee.facilities.TimerPreserveMissed;
31  import javax.transaction.SystemException;
32  
33  import org.jboss.logging.Logger;
34  import org.mobicents.slee.container.SleeContainer;
35  import org.mobicents.slee.container.management.ComponentKey;
36  import org.mobicents.slee.container.management.EventTypeIDImpl;
37  import org.mobicents.slee.runtime.ActivityContext;
38  import org.mobicents.slee.runtime.ActivityContextIDInterface;
39  import org.mobicents.slee.runtime.transaction.SleeTransactionManager;
40  import org.mobicents.slee.runtime.transaction.TransactionManagerImpl;
41  import org.mobicents.slee.runtime.transaction.TransactionalAction;
42  
43  
44  
45  /**
46   * Implementation of the SLEE timer facility.
47   * timer is the timer object currently being examined.
48   * timer.scheduleTime is time that the Timer Event is scheduled to fire.
49   * timer.timeout is timeout for this timer (from TimerOptions).
50   * timer.numRepetitions is the total repetitions for this timer, 0 if infinite,
51   * 1 if non-periodic.
52   *  timer.remainingRepetiton is the remaining repetition count, initially Long.MAX_VALUE
53   * for infinite periodic timers, timer.numRepetitions
54   * otherwise.
55   * timer.period is the timer period (Long.MAX_VALUE if non-periodic).
56   * timer.missed is the counter of undelivered late events.
57   *
58   * @author Tim - major refactoring - prevented thrashing, made threadsafe, added transaction checking
59   * 
60   * @author M. Ranganathan
61   *
62   */
63  public class TimerFacilityImpl implements TimerFacility {
64                     
65      private static final int DEFAULT_TIMEOUT = 100;
66      public static final String JNDI_NAME = "timer";
67      private static final String FQN_PREFIX = "/facilities/timer/";
68      private static final String FQN_TIMERS_PREFIX = FQN_PREFIX + "timers/";    
69      private static String tcache = TransactionManagerImpl.TCACHE;
70      
71      // this is supposed to be the timer resolution in ms of the hosting OS/hardware
72      private int timerResolution = 10;     
73      
74      private Timer timer = new Timer();
75      
76      private SleeContainer serviceContainer;
77      
78      protected EventTypeIDImpl timerEventID;
79      
80      protected ComponentKey timerEventKey;
81      
82      private static Logger logger = Logger.getLogger(TimerFacilityImpl.class);
83      
84      
85      
86      class TimerEventImpl implements TimerEvent {
87          
88          private TimerID timerId;
89          private long scheduledTime;
90          private long expiryTime;
91          private long period;
92          private int numRepetitions;
93          private int remainingRepetitions;
94          private int missedRepetitions;
95          //private ActivityContext ac;
96          //private String activityContextId;
97          private EventTypeID eventTypeID;        
98          //private Address address;
99          
100         TimerEventImpl(TimerID timerId, long scheduledTime, long expiryTime, long period,
101                 int numRepetitions, int remainingRepetitions, int missedRepetitions,
102                 EventTypeID eventTypeID) {
103             this.timerId = timerId;
104           this.scheduledTime = scheduledTime;
105           this.expiryTime = expiryTime;
106           this.period = period;
107           this.numRepetitions = numRepetitions;
108           this.remainingRepetitions = remainingRepetitions;
109           this.missedRepetitions = missedRepetitions;
110           //this.ac = ac;
111           //this.activityContextId = activityContextId;
112           this.eventTypeID = eventTypeID;          
113           //this.address = address;        
114         }
115             
116         public TimerID getTimerID() { return this.timerId; }
117         public long getScheduledTime() { return this.scheduledTime; }
118         public long getExpiryTime() { return this.expiryTime; }
119         public long getPeriod() { return this.period; }
120         public int getNumRepetitions() { return this.numRepetitions; }
121         public int getRemainingRepetitions() { return this.remainingRepetitions; }
122         public int getMissedRepetitions() { return this.missedRepetitions; }               
123         public EventTypeID getEventTypeID() { return this.eventTypeID; }
124         //public Object getEventObject() { return null; }
125         //public Address getAddress() { return this.address; }
126         
127         /*
128         public ActivityContext getActivityContext() {
129             ActivityContext ac =
130                 TimerFacilityImpl.this.serviceContainer.getActivityContextFactory().getActivityContextByKey(this.activityContextId);
131             if (ac == null) logger.error("Can't find ac in cache!");
132             return ac;            
133         } 
134         */
135 
136         /*
137         public String getActivityContextID() {            
138             return getActivityContext().getActivityContextId();
139         }
140         */
141     }
142     
143     class MyTimerTask extends TimerTask implements Serializable {
144         
145         private TimerID timerId;                
146         private String activityContextId;
147         private Address address;
148         private TimerOptions timerOptions;            
149         long startTime;
150         int numRepetitions;
151         int remainingRepetitions;
152         int missedRepetitions;
153         long period;
154         
155         public MyTimerTask(TimerID timerId, String activityContextId, Address address,
156                long startTime, long period, int numRepetitions, TimerOptions timerOptions) {            
157             this.timerId = timerId;
158             //this.aci = aci;
159             this.activityContextId = activityContextId;
160             this.address = address;
161             this.startTime = startTime;
162             this.period = period;
163             this.numRepetitions = numRepetitions;
164             
165             // for infinitely repetitive events the remainingRepetitions value has to be always Int.MAX_VALUE
166             if (numRepetitions == 0) this. remainingRepetitions = Integer.MAX_VALUE;
167               else this.remainingRepetitions = numRepetitions;
168             
169             this.missedRepetitions = 0;
170             this.timerOptions = timerOptions;            
171         }
172         
173         TimerID getTimerID() { return timerId; }
174         
175         /* 
176          * This is where it all happens
177          * 
178          * see SLEE spec 1.0 Sec. 13.1.6
179          * 
180          * I know it doesn't look the same but this follows the pseudo code in 13.1.6!
181          * It's just simplified since the java.util.Timer class takes care of the scheduling stuff.
182          * This allows us to avoid having to wake up every x milliseconds and check our Timers
183          * to see if there's anything to fire, even when there's nothing to do.
184          * If we use the java.util.Timer class with scheduleAtFixedRate then it 
185          * optimise the scheduling to avoid unnecessary polling.         
186          * 
187          * @see java.lang.Runnable#run()
188          */
189         public void run() {            
190             //TODO PERSISTENT TIMERS!!
191             
192             logger.debug("In MyTimerTask.run()");
193             
194             long tRes = timerResolution;
195             long tSys = System.currentTimeMillis();
196             long tDto = DEFAULT_TIMEOUT;
197             long tFire = tSys + tRes;
198             
199             boolean postIt = false;
200             
201             if (this.timerOptions.getPreserveMissed() == TimerPreserveMissed.ALL) {
202                 /* Always post the event. Remember, this method will get called for late events since
203                  * this TimerTask was scheduled to run at fixed rate. see Timer.scheduleAtFixedRate()
204                  */                
205                 postIt = true;
206                 logger.debug("TimerPreserveMissed.ALL so posting the event");
207             } else {
208                 long timeOut;                
209                 if (this.timerOptions.getTimeout() == 0) {
210                     timeOut = tDto;
211                 } else {
212                     timeOut = this.timerOptions.getTimeout();
213                 }
214                 timeOut = Math.min(Math.max(timeOut, tRes), this.period);
215                 logger.debug("I'm using " + timeOut + " for the timeout to work out whether the event's late");
216                 
217                 if (this.timerOptions.getPreserveMissed() == TimerPreserveMissed.NONE) {
218                     //If events are late we NEVER want to post them
219                     logger.debug("TimerPreserveMissed.NONE");
220                   if (tSys <= this.scheduledExecutionTime() + timeOut) {
221                       //Event is not late                      
222                       postIt = true;
223                       logger.debug("Event is NOT late so I'm posting it");
224                   } else {
225                       //Event is late so NOT posting it
226                       logger.debug("Event is late so I'm NOT posting it");
227                       this.missedRepetitions++;
228                   }
229                 } else if (this.timerOptions.getPreserveMissed() == TimerPreserveMissed.LAST) {
230                     //Count missed events.
231                     //Preserve the last missed event
232                     logger.debug("TimerPreserveMissed.LAST");
233                     
234                     if (remainingRepetitions > 1 &&
235                         (tSys > this.scheduledExecutionTime() + timeOut)) {
236                         //Event is not the last one and event is late
237                         logger.debug("Event is late and NOT the last one so I'm NOT posting it");
238                         this.missedRepetitions++;                        
239                     }
240                     else {                        
241                         logger.debug("Event is either NOT late, or late and is the last event so I'm posting it");
242                         postIt = true;
243                     }                    
244                 }               
245             }
246             
247             logger.debug("SCHEDULED EXECUTION TIME IS " + this.scheduledExecutionTime());
248             logger.debug("Remaining repetitions:" + this.remainingRepetitions);
249             
250             decrementRemainingRepetitions();
251 
252             if (postIt) {
253                 //Post the event
254                 TimerEventImpl timerEvent =
255                     new TimerEventImpl(this.timerId, this.scheduledExecutionTime(), tSys, this.period,
256                             this.numRepetitions, this.remainingRepetitions, this.missedRepetitions,
257                             timerEventID);
258                 this.missedRepetitions = 0;
259                 postEvent(timerEvent);
260             } 
261                                     
262             if (remainingRepetitions == 0) {                
263                 //Remove reference to the Timer so the ActivityContext can be reclaimed Spec 13.1.2.1
264                 logger.debug("Timer has expired - removing it");                                
265                 cancelTimer(timerId);
266             }                               
267         }
268         
269         /**
270          * Decrement remainingRepetitions by 1 if this is not an infinitely repeatable timer
271          */
272         private void decrementRemainingRepetitions() {
273             if (numRepetitions > 0) this.remainingRepetitions--;
274         }
275 
276         void postEvent(TimerEventImpl timerEvent) {            
277              logger.debug("Posting timer event:" + timerEvent);              
278              //Post the timer event to the queue.
279              try {
280                
281                Object act = serviceContainer.getActivityContextFactory().getActivityFromKey(this.activityContextId);
282                
283                serviceContainer.getSleeEndpoint().enqueueEvent(timerEventID, timerEvent,
284                    act, this.address);
285                
286                
287                
288              } catch(InvalidStateException e) {
289                logger.error("Failed to send timer event", e);
290              }
291          }
292 
293         /**
294          * @return the activity context interface for the timer task.
295          */
296         public String getActivityContextId() {
297             // TODO Auto-generated method stub
298             return this.activityContextId;
299         }
300     }
301     
302     
303     class TimerFacilityAction implements TransactionalAction {
304         
305         private static final int TYPE_SET_ONETIME = 0;
306         private static final int TYPE_SET_PERIOD = 1;
307         private static final int TYPE_CANCEL = 2;
308         
309         private TimerTask task;
310         private Date startTime;
311         private long period;
312         private TimerID timerID;
313         
314         private int actionType; // Saves having to define a new class when you have several actions
315                 
316         public String toString() {
317             return this.getClass() + " Type: " + actionType;
318         }
319         
320         TimerFacilityAction(MyTimerTask task, Date startTime, long period) {
321             this(task, startTime);    
322             this.period = period;          
323             actionType = TYPE_SET_PERIOD;
324             
325             this.timerID = task.getTimerID();
326         }
327         
328         TimerFacilityAction(MyTimerTask task, Date startTime) {           
329             this.task = task;
330             this.startTime = startTime;
331             actionType = TYPE_SET_ONETIME;
332             
333             this.timerID = task.getTimerID();
334         }
335         
336         TimerFacilityAction(MyTimerTask task) {           
337             this.task = task;            
338             actionType = TYPE_CANCEL;
339         }
340         
341         public void execute() {
342             TimerFacilityImpl tf = TimerFacilityImpl.this;
343             if (actionType == TYPE_SET_ONETIME) {
344                 logger.debug("===Scheduling one-time timer");
345                 tf.timer.schedule(task, startTime);
346             } else if (actionType == TYPE_SET_PERIOD){
347                 tf.timer.scheduleAtFixedRate(task, startTime, period);
348                 logger.debug("===Scheduling periodic timer");
349             } else if (actionType == TYPE_CANCEL) {
350                 logger.debug("===Cancelling timer");                
351                 this.task.cancel();
352             }
353         }
354     }
355     
356     public TimerFacilityImpl(SleeContainer serviceContainer) {        
357         this.serviceContainer = serviceContainer; 
358         this.timerEventKey = new ComponentKey("javax.slee.facilities.TimerEvent", "javax.slee", "1.0");
359         this.timerEventID = this.serviceContainer.getEventType(timerEventKey);  
360     }
361     
362     /* One shot timer
363      * @see javax.slee.facilities.TimerFacility#setTimer(javax.slee.ActivityContextInterface, javax.slee.Address, long, javax.slee.facilities.TimerOptions)
364      */
365     public TimerID setTimer(ActivityContextInterface aci, Address address,
366                     long startTime, TimerOptions timerOptions) throws NullPointerException,
367             IllegalArgumentException,  FacilityException {
368                 
369           if (aci == null ) throw new NullPointerException("Null ActivityContextInterface");
370           if (timerOptions == null) throw new NullPointerException("Null TimerOptions");
371           if (startTime < 0) throw new IllegalArgumentException("startTime < 0");      
372           logger.debug("setTimer " + ((ActivityContextIDInterface)aci).retrieveActivityContextID());
373           
374           SleeTransactionManager txMgr = SleeContainer.getTransactionManager();
375           
376           boolean startedTx = txMgr.requireTransaction();
377                                         
378           TimerIDImpl timerID = new TimerIDImpl();
379           logger.debug("Timer id is: " + timerID);
380                     
381           long now = System.currentTimeMillis();
382           if (startTime < now) startTime = now;
383           
384           MyTimerTask task = new MyTimerTask(timerID, 
385                   ((ActivityContextIDInterface)aci).retrieveActivityContextID(),
386                   address, startTime,
387                   Long.MAX_VALUE, 1, timerOptions);
388           
389           /* put it into the treecache in it's own node since we do not want to lock all the timers
390            * we add the whole object in, rather than the attributes since we're not interested
391            * in being able to rollback changes to the timer
392            */
393           try {
394               txMgr.putObject(tcache,FQN_TIMERS_PREFIX + timerID.toString(), "task", task);
395           } catch (SystemException e) {
396               throw new FacilityException("Failed to add timer to cache", e);
397           }
398           logger.debug("Added new timer to cache");
399           
400           //Attach to activity context
401           ((ActivityContextIDInterface)aci).retrieveActivityContext().attachTimer(timerID);
402                     
403           //Create an action that actually schedules the timer - we execute this on commit of the tx
404           TimerFacilityAction action = new TimerFacilityAction(task, new Date(startTime));                 
405                           
406           try {
407               SleeContainer.getTransactionManager().addAfterCommitAction(action);
408           } catch (SystemException e) {
409               //This only happens if it is not in a transaction - this should never happen
410               logger.error(e);
411           }
412           
413           //addToMaps(timerId, task);
414           
415           //If we started a tx for this operation, we commit it now
416           if (startedTx) {
417               try {
418                   SleeContainer.getTransactionManager().commit();
419               } catch(Exception e) {                  
420                   throw new TransactionRolledbackLocalException("Failed to commit transaction");
421               }
422           }
423         
424           return timerID;        
425     }
426     
427 
428 
429     /* Periodic timer
430      * @see javax.slee.facilities.TimerFacility#setTimer(javax.slee.ActivityContextInterface, javax.slee.Address, long, long, int, javax.slee.facilities.TimerOptions)
431      */
432     public TimerID setTimer(ActivityContextInterface aci, Address address,
433                     long startTime, long period, int numRepetitions, TimerOptions timerOptions)
434             throws NullPointerException, IllegalArgumentException,
435               TransactionRolledbackLocalException, FacilityException {
436 
437         if (aci == null ) throw new NullPointerException("Null ActivityContextInterface");
438         if (timerOptions == null) throw new NullPointerException("Null TimerOptions");
439         if (startTime < 0) throw new IllegalArgumentException("startTime < 0");
440         if (period <= 0) throw new IllegalArgumentException("period <= 0");
441         if (numRepetitions < 0) throw new IllegalArgumentException("numRepetitions < 0");
442         if (timerOptions.getTimeout() > period) throw new IllegalArgumentException("timeout > period"); //SPEC 13.1.3
443         if (timerOptions.getTimeout() < this.getResolution())
444             timerOptions.setTimeout(Math.min(period, this.getResolution())); //SPEC 13.1.3
445         
446         SleeTransactionManager txMgr = SleeContainer.getTransactionManager();
447         
448         boolean startedTx = txMgr.requireTransaction();
449         
450         
451         logger.debug("setTimer: startTime = " + startTime + " period = " +
452                    period + " numRepetitions = " + numRepetitions + " timeroptions =" + timerOptions );                
453         
454         TimerIDImpl timerID = new TimerIDImpl();
455         logger.debug("Timer id is: " + timerID);
456         
457         long now = System.currentTimeMillis();
458         if (startTime < now) startTime = now;
459                 
460         logger.debug("START TIME IS " + startTime);
461         
462         MyTimerTask task = new MyTimerTask(timerID,
463                 ((ActivityContextIDInterface)aci).retrieveActivityContextID(),
464                 address, startTime,
465         period, numRepetitions, timerOptions);
466                                        
467         /* put it into the treecache in it's own node since we do not want to lock all the timers
468          * we add the whole object in, rather than the attributes since we're not interested
469          * in being able to rollback changes to the timer
470          */
471         try {
472             txMgr.putObject(tcache,FQN_TIMERS_PREFIX + timerID.toString(), "task", task);
473         } catch (SystemException e) {
474             throw new FacilityException("Failed to add timer to cache", e);
475         }
476         logger.debug("Added new timer to cache");
477         
478         //Attach to activity context
479         ((ActivityContextIDInterface)aci).retrieveActivityContext().attachTimer(timerID);
480         
481         // Create an action that actually schedules the timer - we execute this on commit of the tx
482         TimerFacilityAction action = new TimerFacilityAction(task, new Date(startTime), period);
483                         
484         try {
485             SleeContainer.getTransactionManager().addAfterCommitAction(action);
486         } catch (SystemException e) {
487             //This only happens if it is not in a transaction - this should never happen
488             logger.error(e);
489         }
490                         
491         //addToMaps(timerId, task);
492         
493         //If we started a tx for this operation, we commit it now
494         if (startedTx) {
495             try {
496                 SleeContainer.getTransactionManager().commit();
497             } catch(Exception e) {                  
498                 throw new TransactionRolledbackLocalException("Failed to commit transaction");
499             }
500         }
501         
502         return timerID;
503     }
504 
505     /* (non-Javadoc)
506      * @see javax.slee.facilities.TimerFacility#cancelTimer(javax.slee.facilities.TimerID)
507      */
508     public synchronized void cancelTimer(TimerID timerID) throws NullPointerException,
509             TransactionRolledbackLocalException, FacilityException {
510                   
511         if (timerID == null ) throw new NullPointerException("Null TimerID");
512         
513         SleeTransactionManager txMgr = SleeContainer.getTransactionManager();
514         
515         boolean startedTx = txMgr.requireTransaction();
516         
517         logger.debug("Started tx: " + startedTx);
518                 
519         logger.debug("Cancelling timer");
520         
521         //Remove the timer from the tree cache
522         String fqn = FQN_TIMERS_PREFIX + timerID.toString();
523         logger.debug("Removing node: " + fqn);
524         MyTimerTask task = null;
525         try {
526             
527             task = (MyTimerTask)txMgr.getObject(tcache,fqn, "task");
528             if (task == null) {
529                 logger.debug("Can't find timer task in cache!");
530                 return;
531             }
532             
533             //Detach this timer from the ac
534             ActivityContext ac =
535                 this.serviceContainer.getActivityContextFactory().getActivityContextByKey(task.getActivityContextId());
536             if (ac == null) throw new FacilityException("Can't find ac in cache!");
537             
538             ac.detachTimer(timerID);
539             
540             //Remove the node
541             txMgr.removeNode(tcache,fqn);
542         } catch (SystemException e) {
543             throw new FacilityException("Failed to remove timer from cache", e);
544         }
545         
546  
547         
548         /* Hack
549          * It seems that, if there's a transactional action to set a timer to start
550          * immediately, and there's a cancel for that timer in the same transaction.
551          * Then, when the transaction commits, the timer should never start ticking, even
552          * thought the cancel action is executed after the setimer action
553          * (see TCK test 1170)
554          * This means, when we add a cancel timer action to the transaction, we only actually add
555          * it if the corresponding settimer method isn't in the transaction - otherwise we just remove
556          * the first settime
557          * 
558          */
559         boolean needToAdd = true;
560         
561         try {
562             List actions = ((TransactionManagerImpl)txMgr).getCommitActions();
563             if (actions != null) {
564               Iterator iter = actions.iterator();
565               logger.debug("There are " + actions.size() + " actions");
566               while (iter.hasNext()) {
567                   TransactionalAction action = (TransactionalAction)iter.next();
568                   
569                   if (action instanceof TimerFacilityAction) {
570                       logger.debug("Timerfacilityaction");
571                       TimerFacilityAction tfAction = (TimerFacilityAction)action;
572                       logger.debug("Action 2 is: " + tfAction);
573                       logger.debug("timerid is: " + tfAction.timerID);
574                       logger.debug("actiontype is: " + tfAction.actionType);
575                       logger.debug("timerid is: " + timerID);
576                       if (((tfAction.actionType == TimerFacilityAction.TYPE_SET_ONETIME) ||
577                               (tfAction.actionType == TimerFacilityAction.TYPE_SET_PERIOD)) &&                                                        
578                               tfAction.timerID.equals(timerID))
579                       {
580                           logger.debug("Removing it:");
581                           //Remove it
582                           iter.remove();
583                           needToAdd = false;
584                                                                                                       
585                           break;
586                       }
587                   }               
588               }
589             }
590         } catch(SystemException e) {
591             throw new FacilityException("Failed to get actions", e);
592         }
593         
594         logger.debug("Need to add: " + needToAdd);
595                 
596         //Add an action representing the cancelling of the timer task
597         if (needToAdd) {
598           TimerFacilityAction action = new TimerFacilityAction(task);        
599       try {        
600            SleeContainer.getTransactionManager().addAfterCommitAction(action);
601            logger.debug("Added cancel timer commit action");
602           
603       } catch (SystemException e) {
604           //This only happens if it is not in a transaction - this should never happen
605           logger.error(e);
606       }
607         }
608     
609     if (startedTx) {
610             try {
611               logger.debug("started tx so committing it");
612               SleeContainer.getTransactionManager().commit();
613             } catch(Exception e) {                  
614                 throw new TransactionRolledbackLocalException("Failed to commit transaction");
615             }
616         }
617     }
618 
619     /* (non-Javadoc)
620      * @see javax.slee.facilities.TimerFacility#getResolution()
621      */
622     public long getResolution() throws FacilityException {
623         return this.timerResolution;
624     }
625 
626     /* (non-Javadoc)
627      * @see javax.slee.facilities.TimerFacility#getDefaultTimeout()
628      */
629     public long getDefaultTimeout() throws FacilityException {
630         return DEFAULT_TIMEOUT;
631     }
632     
633     /** stop the timer. This is for JMX management interfaces
634      *
635      */
636     public void stop() {
637         this.timer.cancel();
638     }
639     
640     /**
641      * Start the timer. This is for jmx management interfaces.
642      *
643      */
644     public void start() {        
645     }
646     
647     /**
648      * Set the timer resolution. This is for jmx interfaces.
649      * 
650      * @throws Exception if the resolution is smaller than 10 milisecods
651      */
652     public void setTimerResolution(int resolution) {
653         if (resolution < 10 ) throw new IllegalArgumentException("min resolution is 10 miliseconds");
654         else this.timerResolution = resolution;
655     }
656     
657     /** 
658      * Get the timer resolution. This is for jmx interfaces.
659      *
660      */
661     public int getTimerResolution() {
662         return this.timerResolution;
663     }
664     
665 
666 }
667