View Javadoc

1   /*
2    *  jDTAUS Core RI Task Monitor
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.core.monitor.ri;
22  
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Locale;
27  import java.util.Map;
28  import javax.swing.event.EventListenerList;
29  import org.jdtaus.core.container.ContainerFactory;
30  import org.jdtaus.core.logging.spi.Logger;
31  import org.jdtaus.core.monitor.Task;
32  import org.jdtaus.core.monitor.TaskEvent;
33  import org.jdtaus.core.monitor.TaskListener;
34  import org.jdtaus.core.monitor.spi.TaskMonitor;
35  import org.jdtaus.core.text.Message;
36  
37  /**
38   * jDTAUS Core SPI {@code TaskMonitor} reference implementation.
39   * <p>The reference implementation uses a thread checking the state of all tasks
40   * in the system periodically which is started upon initialization and runs
41   * endlessly. Monitoring is controlled by property {@code pollIntervalMillis}
42   * specifying the milliseconds of one period. Each time a period ends, tasks
43   * are checked for state changes and corresponding events are fired. Property
44   * {@code pollIntervalMillis} defaults to {@code 250ms}.</p>
45   *
46   * <p><b>Note:</b><br/>
47   * {@code TaskEvent}s of type {@code STARTED} and {@code ENDED} are fired by the
48   * thread executing the task's operation. Since tasks are monitored
49   * asynchronously, {@code TaskEvent}s of type {@code CHANGED_STATE} are fired by
50   * the monitor thread, not by the thread executing the task's operation. Make
51   * sure {@code TaskListener} implementations are prepared for being notified
52   * by a different thread than the one executing a task's operation.</p>
53   *
54   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
55   * @version $JDTAUS: DefaultTaskMonitor.java 8641 2012-09-27 06:45:17Z schulte $
56   *
57   * @see org.jdtaus.core.container.Container
58   */
59  public class DefaultTaskMonitor implements TaskMonitor
60  {
61      //--Constructors------------------------------------------------------------
62  
63  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausConstructors
64      // This section is managed by jdtaus-container-mojo.
65  
66      /** Standard implementation constructor <code>org.jdtaus.core.monitor.ri.DefaultTaskMonitor</code>. */
67      public DefaultTaskMonitor()
68      {
69          super();
70      }
71  
72  // </editor-fold>//GEN-END:jdtausConstructors
73  
74      //------------------------------------------------------------Constructors--
75      //--Dependencies------------------------------------------------------------
76  
77  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
78      // This section is managed by jdtaus-container-mojo.
79  
80      /**
81       * Gets the configured <code>Logger</code> implementation.
82       *
83       * @return The configured <code>Logger</code> implementation.
84       */
85      private Logger getLogger()
86      {
87          return (Logger) ContainerFactory.getContainer().
88              getDependency( this, "Logger" );
89  
90      }
91  
92      /**
93       * Gets the configured <code>TaskListener</code> implementation.
94       *
95       * @return The configured <code>TaskListener</code> implementation.
96       */
97      private TaskListener[] getTaskListener()
98      {
99          return (TaskListener[]) ContainerFactory.getContainer().
100             getDependency( this, "TaskListener" );
101 
102     }
103 
104     /**
105      * Gets the configured <code>Locale</code> implementation.
106      *
107      * @return The configured <code>Locale</code> implementation.
108      */
109     private Locale getLocale()
110     {
111         return (Locale) ContainerFactory.getContainer().
112             getDependency( this, "Locale" );
113 
114     }
115 
116 // </editor-fold>//GEN-END:jdtausDependencies
117 
118     //------------------------------------------------------------Dependencies--
119     //--Properties--------------------------------------------------------------
120 
121 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
122     // This section is managed by jdtaus-container-mojo.
123 
124     /**
125      * Gets the value of property <code>defaultPollIntervalMillis</code>.
126      *
127      * @return Default number of milliseconds per poll interval.
128      */
129     private java.lang.Long getDefaultPollIntervalMillis()
130     {
131         return (java.lang.Long) ContainerFactory.getContainer().
132             getProperty( this, "defaultPollIntervalMillis" );
133 
134     }
135 
136 // </editor-fold>//GEN-END:jdtausProperties
137 
138     //--------------------------------------------------------------Properties--
139     //--TaskEventSource---------------------------------------------------------
140 
141     public void addTaskListener( final TaskListener listener )
142     {
143         if ( listener == null )
144         {
145             throw new NullPointerException( "listener" );
146         }
147 
148         this.taskListeners.add( TaskListener.class, listener );
149     }
150 
151     public void removeTaskListener( final TaskListener listener )
152     {
153         if ( listener == null )
154         {
155             throw new NullPointerException( "listener" );
156         }
157 
158         this.taskListeners.remove( TaskListener.class, listener );
159     }
160 
161     public TaskListener[] getTaskListeners()
162     {
163         return (TaskListener[]) this.taskListeners.getListeners(
164             TaskListener.class );
165 
166     }
167 
168     //---------------------------------------------------------TaskEventSource--
169     //--TaskMonitor-------------------------------------------------------------
170 
171     public void monitor( final Task task )
172     {
173         if ( task == null )
174         {
175             throw new NullPointerException( "task" );
176         }
177 
178         synchronized ( this.stateMap )
179         {
180             this.checkMonitorThread();
181             this.createTaskState( task );
182             this.fireTaskEvent( new TaskEvent( task, TaskEvent.STARTED ) );
183         }
184     }
185 
186     public void finish( final Task task )
187     {
188         if ( task == null )
189         {
190             throw new NullPointerException( "task" );
191         }
192 
193         synchronized ( this.stateMap )
194         {
195             if ( this.changedState( task ) )
196             {
197                 this.fireTaskEvent( new TaskEvent( task,
198                                                    TaskEvent.CHANGED_STATE ) );
199 
200             }
201 
202             this.removeTaskState( task );
203             this.fireTaskEvent( new TaskEvent( task, TaskEvent.ENDED ) );
204         }
205     }
206 
207     //-------------------------------------------------------------TaskMonitor--
208     //--DefaultTaskMonitor------------------------------------------------------
209 
210     /** List of {@code TaskListener}s. */
211     private final EventListenerList taskListeners = new EventListenerList();
212 
213     /** The thread monitoring tasks. */
214     private MonitorThread monitorThread;
215 
216     /** Maps {@code Task}s to corresponding {@code TaskState} instances. */
217     private final Map stateMap = new HashMap( 1000 );
218 
219     /** Number of milliseconds per poll interval. */
220     private Long pollIntervalMillis;
221 
222     /**
223      * Creates a new {@code DefaultTaskMonitor} instance taking the
224      * milliseconds of one period.
225      *
226      * @param pollIntervalMillis the number of milliseconds per poll interval.
227      */
228     public DefaultTaskMonitor( final long pollIntervalMillis )
229     {
230         if ( pollIntervalMillis > 0L )
231         {
232             this.pollIntervalMillis = new Long( pollIntervalMillis );
233         }
234     }
235 
236     /**
237      * Gets the value of property {@code pollIntervalMillis}.
238      *
239      * @return the number of milliseconds per poll interval.
240      */
241     private long getPollIntervalMillis()
242     {
243         if ( this.pollIntervalMillis == null )
244         {
245             this.pollIntervalMillis = this.getDefaultPollIntervalMillis();
246         }
247 
248         return this.pollIntervalMillis.longValue();
249     }
250 
251     /** Caches the state of a task. */
252     private static final class TaskState
253     {
254 
255         boolean indeterminate;
256 
257         boolean cancelable;
258 
259         boolean cancelled;
260 
261         int minimum;
262 
263         int maximum;
264 
265         int progress;
266 
267         Message progressDescription;
268 
269     }
270 
271     /** Thread monitoring all currently running {@code Task}s for changes. */
272     private class MonitorThread extends Thread
273     {
274 
275         /** Milliseconds per poll interval. */
276         private final long pollIntervalMillis;
277 
278         /** Creates a new {@code MonitorThread} instance. */
279         private MonitorThread( final long pollIntervalMillis )
280         {
281             super( "DefaultTaskMonitor" );
282             this.pollIntervalMillis = pollIntervalMillis;
283         }
284 
285         /** {@inheritDoc} */
286         public void run()
287         {
288             while ( true )
289             {
290                 try
291                 {
292                     Thread.sleep( this.pollIntervalMillis );
293                     this.checkTasks();
294                 }
295                 catch ( InterruptedException e )
296                 {
297                     this.checkTasks();
298                 }
299             }
300         }
301 
302         public void start()
303         {
304             super.start();
305             getLogger().debug( getThreadStartedMessage(
306                 getLocale(), new Long( this.pollIntervalMillis ) ) );
307 
308         }
309 
310         /**
311          * Checks the state of all currently running tasks for changes and
312          * fires corresponding events.
313          */
314         private void checkTasks()
315         {
316             synchronized ( DefaultTaskMonitor.this.stateMap )
317             {
318                 for ( Iterator it = DefaultTaskMonitor.this.stateMap.keySet().
319                     iterator(); it.hasNext(); )
320                 {
321                     final Task task = (Task) it.next();
322                     if ( changedState( task ) )
323                     {
324                         fireTaskEvent( new TaskEvent(
325                             task, TaskEvent.CHANGED_STATE ) );
326 
327                     }
328                 }
329             }
330         }
331 
332     }
333 
334     /**
335      * Gets the monitor thread.
336      *
337      * @return the monitor thread.
338      */
339     private synchronized void checkMonitorThread()
340     {
341         if ( this.monitorThread == null )
342         {
343             this.monitorThread =
344             new MonitorThread( this.getPollIntervalMillis() );
345 
346             this.monitorThread.start();
347         }
348 
349         if ( !this.monitorThread.isAlive() )
350         { // Monitoring died for some reason; start a new thread.
351             this.getLogger().warn( this.getThreadDiedMessage(
352                 this.getLocale() ) );
353 
354             this.monitorThread =
355             new MonitorThread( this.getPollIntervalMillis() );
356 
357             this.monitorThread.start();
358         }
359     }
360 
361     /**
362      * Notifies all registered {@code TaskListener}s about {@code TaskEvent}s.
363      *
364      * @param e The event to be provided to the listeners.
365      */
366     private void fireTaskEvent( final TaskEvent e )
367     {
368         if ( e == null )
369         {
370             throw new NullPointerException( "e" );
371         }
372 
373 
374         final Object[] listeners = this.taskListeners.getListenerList();
375         for ( int i = listeners.length - 2; i >= 0; i -= 2 )
376         {
377             if ( listeners[i] == TaskListener.class )
378             {
379                 ( (TaskListener) listeners[i + 1] ).onTaskEvent( e );
380             }
381         }
382 
383         final TaskListener[] taskListener = this.getTaskListener();
384         for ( int i = taskListener.length - 1; i >= 0; i-- )
385         {
386             taskListener[i].onTaskEvent( e );
387         }
388     }
389 
390     /**
391      * Caches the state of a {@code Task}.
392      *
393      * @param task the task to cache state for.
394      *
395      * @throws NullPointerException if {@code task} is {@code null}.
396      * @throws IllegalStateException if the cache already holds state for
397      * {@code task}.
398      */
399     private void createTaskState( final Task task )
400     {
401         final TaskState state = new TaskState();
402         state.cancelable = task.isCancelable();
403         state.indeterminate = task.isIndeterminate();
404         state.cancelled = state.cancelable
405                           ? task.isCancelled()
406                           : false;
407 
408         state.progressDescription = task.getProgressDescription();
409 
410         if ( state.indeterminate )
411         {
412             state.maximum = Integer.MIN_VALUE;
413             state.minimum = Integer.MIN_VALUE;
414             state.progress = Integer.MIN_VALUE;
415         }
416         else
417         {
418             state.maximum = task.getMaximum();
419             state.minimum = task.getMinimum();
420             state.progress = task.getProgress();
421         }
422 
423         if ( this.stateMap.put( task, state ) != null )
424         {
425             throw new IllegalStateException( this.getTaskAlreadyStartedMessage(
426                 this.getLocale(),
427                 task.getDescription().getText( this.getLocale() ),
428                 new Date( task.getTimestamp() ) ) );
429 
430         }
431     }
432 
433     /**
434      * Removes the cached state of a {@code Task}.
435      *
436      * @param task the task to remove the cached state of.
437      *
438      * @throws NullPointerException if {@code task} is {@code null}.
439      */
440     private void removeTaskState( final Task task )
441     {
442         if ( task == null )
443         {
444             throw new NullPointerException( "task" );
445         }
446 
447         this.stateMap.remove( task );
448     }
449 
450     /**
451      * Checks the state of a given task for changes.
452      *
453      * @param task the task to check for state changes.
454      *
455      * @return {@code true} if the state of {@code task} changed since the last
456      * time this method got called; {@code false} if the state did not change.
457      *
458      * @throws NullPointerException if {@code task} is {@code null}.
459      * @throws IllegalStateException if no cached state exists for {@code task}.
460      */
461     private boolean changedState( final Task task )
462     {
463         if ( task == null )
464         {
465             throw new NullPointerException( "task" );
466         }
467 
468 
469         boolean changedState = false;
470         final TaskState state = (TaskState) this.stateMap.get( task );
471 
472         if ( state == null )
473         {
474             throw new IllegalStateException();
475         }
476 
477         if ( state.indeterminate )
478         {
479             state.indeterminate = task.isIndeterminate();
480             if ( !state.indeterminate )
481             {
482                 state.minimum = task.getMinimum();
483                 state.maximum = task.getMaximum();
484                 state.progress = task.getProgress();
485                 changedState = true;
486             }
487         }
488         else
489         {
490             state.indeterminate = task.isIndeterminate();
491             if ( state.indeterminate )
492             {
493                 changedState = true;
494             }
495             else
496             {
497                 if ( state.minimum != task.getMinimum() )
498                 {
499                     state.minimum = task.getMinimum();
500                     changedState = true;
501                 }
502                 if ( state.maximum != task.getMaximum() )
503                 {
504                     state.maximum = task.getMaximum();
505                     changedState = true;
506                 }
507                 if ( state.progress != task.getProgress() )
508                 {
509                     state.progress = task.getProgress();
510                     changedState = true;
511                 }
512             }
513         }
514 
515         if ( state.cancelable )
516         {
517             state.cancelable = task.isCancelable();
518             if ( !state.cancelable )
519             {
520                 changedState = true;
521             }
522             else
523             {
524                 if ( state.cancelled != task.isCancelled() )
525                 {
526                     state.cancelled = task.isCancelled();
527                     changedState = true;
528                 }
529             }
530         }
531         else
532         {
533             state.cancelable = task.isCancelable();
534             if ( !state.cancelable )
535             {
536                 state.cancelled = false;
537                 changedState = true;
538             }
539         }
540 
541         if ( state.progressDescription != task.getProgressDescription() )
542         {
543             state.progressDescription = task.getProgressDescription();
544             changedState = true;
545         }
546         else if ( state.progressDescription != null &&
547                   !state.progressDescription.getText( this.getLocale() ).
548             equals( task.getProgressDescription().getText(
549             this.getLocale() ) ) )
550         {
551             changedState = true;
552         }
553 
554         return changedState;
555     }
556 
557     //------------------------------------------------------DefaultTaskMonitor--
558     //--Messages----------------------------------------------------------------
559 
560 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
561     // This section is managed by jdtaus-container-mojo.
562 
563     /**
564      * Gets the text of message <code>threadStarted</code>.
565      * <blockquote><pre>Neuen Thread gestartet. Abtastperiode {0,number}ms.</pre></blockquote>
566      * <blockquote><pre>New thread started. Period {0,number}ms.</pre></blockquote>
567      *
568      * @param locale The locale of the message instance to return.
569      * @param periodMillis Period of the started thread.
570      *
571      * @return Information about a started thread.
572      */
573     private String getThreadStartedMessage( final Locale locale,
574             final java.lang.Number periodMillis )
575     {
576         return ContainerFactory.getContainer().
577             getMessage( this, "threadStarted", locale,
578                 new Object[]
579                 {
580                     periodMillis
581                 });
582 
583     }
584 
585     /**
586      * Gets the text of message <code>threadDied</code>.
587      * <blockquote><pre>Thread abnorm beendet. Konsultieren Sie die Protokolle für mögliche Ursachen.</pre></blockquote>
588      * <blockquote><pre>The monitor thread terminated abnormally. Consult the logs for any causes.</pre></blockquote>
589      *
590      * @param locale The locale of the message instance to return.
591      *
592      * @return Information about a dead thread.
593      */
594     private String getThreadDiedMessage( final Locale locale )
595     {
596         return ContainerFactory.getContainer().
597             getMessage( this, "threadDied", locale, null );
598 
599     }
600 
601     /**
602      * Gets the text of message <code>taskAlreadyStarted</code>.
603      * <blockquote><pre>Ein Vorgang mit Beschreibung {0} wurde bereits um {1,time,long} gestartet.</pre></blockquote>
604      * <blockquote><pre>A task with description {0} already has been started at {1,time,long}.</pre></blockquote>
605      *
606      * @param locale The locale of the message instance to return.
607      * @param taskDescription Description of the already running task.
608      * @param startTime Time the already running task got started.
609      *
610      * @return Information about an already running task.
611      */
612     private String getTaskAlreadyStartedMessage( final Locale locale,
613             final java.lang.String taskDescription,
614             final java.util.Date startTime )
615     {
616         return ContainerFactory.getContainer().
617             getMessage( this, "taskAlreadyStarted", locale,
618                 new Object[]
619                 {
620                     taskDescription,
621                     startTime
622                 });
623 
624     }
625 
626 // </editor-fold>//GEN-END:jdtausMessages
627 
628     //----------------------------------------------------------------Messages--
629 }