KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > avalon > cornerstone > blocks > scheduler > DefaultTimeScheduler


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12  * implied.
13  *
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.avalon.cornerstone.blocks.scheduler;
19
20 import java.util.ArrayList JavaDoc;
21 import java.util.Collection JavaDoc;
22 import java.util.Hashtable JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.NoSuchElementException JavaDoc;
25 import java.util.Vector JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28
29 import org.apache.avalon.cornerstone.services.scheduler.Target;
30 import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
31 import org.apache.avalon.cornerstone.services.scheduler.TimeTrigger;
32 import org.apache.avalon.cornerstone.services.scheduler.TriggerFailureListener;
33 import org.apache.avalon.cornerstone.services.threads.ThreadManager;
34
35 import org.apache.avalon.framework.activity.Disposable;
36 import org.apache.avalon.framework.activity.Startable;
37 import org.apache.avalon.framework.logger.AbstractLogEnabled;
38 import org.apache.avalon.framework.service.ServiceException;
39 import org.apache.avalon.framework.service.ServiceManager;
40 import org.apache.avalon.framework.service.Serviceable;
41
42 /**
43  * Default implementation of TimeScheduler service.
44  *
45  * @author <a HREF="mailto:dev@avalon.apache.org">Avalon Development Team</a>
46  * @avalon.component name="time-scheduler" lifestyle="singleton"
47  * @avalon.service type="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler"
48  */

49 public class DefaultTimeScheduler
50     extends AbstractLogEnabled
51     implements TimeScheduler, Serviceable, Startable, Disposable, Runnable JavaDoc, MonitorableTimeSchedulerMBean
52 {
53     // ----------------------------------------------------------------------
54
// Properties
55
// ----------------------------------------------------------------------
56
private final Hashtable JavaDoc m_entries = new Hashtable JavaDoc();
57     private final PriorityQueue m_priorityQueue =
58         new SynchronizedPriorityQueue( new BinaryHeap() );
59     private ThreadManager m_threadManager;
60     private boolean m_running;
61     private ArrayList JavaDoc m_triggerFailureListeners = new ArrayList JavaDoc();
62
63     // ----------------------------------------------------------------------
64
// Getter/Setter methods
65
// ----------------------------------------------------------------------
66
//
67
// LSD: these have been added in to allow subclasses of the
68
// DefaultScheduler to override implementation behaviour.
69
// You should *not* make these public in subclasses (hence
70
// they are final); they're here for convenience implementation
71
// only.
72

73     protected final ThreadManager getThreadManager()
74     {
75         return m_threadManager;
76     }
77
78     protected final boolean isRunning()
79     {
80         return m_running;
81     }
82
83     protected final void setRunning( boolean running )
84     {
85         m_running = running;
86     }
87
88     protected final List JavaDoc getTriggerFailureListeners()
89     {
90         return m_triggerFailureListeners;
91     }
92
93     protected final Map JavaDoc getEntryMap()
94     {
95         return m_entries;
96     }
97
98     protected final PriorityQueue getPriorityQueue()
99     {
100         return m_priorityQueue;
101     }
102
103     // ----------------------------------------------------------------------
104
// Avalon Lifecycle
105
// ----------------------------------------------------------------------
106

107    /**
108     * Supply of the service manager to the component.
109     * @param serviceManager the service manager
110     * @avalon.dependency type="org.apache.avalon.cornerstone.services.threads.ThreadManager"
111     */

112     public void service( final ServiceManager serviceManager )
113         throws ServiceException
114     {
115         m_threadManager = (ThreadManager)serviceManager.lookup( ThreadManager.ROLE );
116     }
117
118     public void dispose()
119     {
120         if( getLogger().isDebugEnabled() )
121         {
122             getLogger().debug( "disposal" );
123         }
124         m_entries.clear();
125         m_priorityQueue.clear();
126     }
127
128     public void start()
129         throws Exception JavaDoc
130     {
131         //this should suck threads from a named pool
132
getThreadManager().getDefaultThreadPool().execute( this );
133     }
134
135     public void stop()
136     {
137         m_running = false;
138         synchronized( this )
139         {
140             notifyAll();
141         }
142     }
143
144     // ----------------------------------------------------------------------
145
// Work Interface: Runnable
146
// ----------------------------------------------------------------------
147
/**
148      * Entry point for thread that monitors entrys and triggers
149      * entrys when necessary.
150      */

151     public void run()
152     {
153         m_running = true;
154
155         while( m_running )
156         {
157             long duration = 0;
158
159             if( !getPriorityQueue().isEmpty() )
160             {
161                 TimeScheduledEntry entry = null;
162                 synchronized( this )
163                 {
164                     entry = getNextEntry();
165                     if( null == entry ) continue;
166
167                     duration = entry.getNextTime() - System.currentTimeMillis();
168
169                     if( duration < 0 )
170                     {
171                         //time to run job so remove it from priority queue
172
//and run it
173
getPriorityQueue().pop();
174
175                         //Note that we need the pop to occur in a
176
//synchronized section while the runEntry
177
//does not need to be synchronized
178
//hence why there is to if statements
179
//structured in this ugly way
180
}
181                 }
182
183                 if( duration < 0 )
184                 {
185                     // runs and reschedules the entry
186
runEntry( entry );
187                     continue;
188                 }
189                 else if( 0 == duration )
190                 {
191                     //give a short duration that will sleep
192
// so that next loop will definetly be below 0.
193
//Can not act on zero else multiple runs could go through
194
//at once
195
duration = 1;
196                 }
197             }
198
199             //wait/sleep until monitor is signalled which occurs when
200
//next jobs is likely to occur or when a new job gets added to
201
//top of heap
202
try
203             {
204                 synchronized( this )
205                 {
206                     wait( duration );
207                 }
208             }
209             catch( final InterruptedException JavaDoc ie )
210             {
211             }
212         }
213     }
214
215     // ----------------------------------------------------------------------
216
// Work Interface: Time Scheduler
217
// ----------------------------------------------------------------------
218
/**
219      * Add a trigger failure listener
220      * @param listener The listener
221      */

222     public void addTriggerFailureListener( TriggerFailureListener listener )
223     {
224         getTriggerFailureListeners().add( listener );
225     }
226
227     /**
228      * Remove a trigger failure listener
229      * @param listener The listener
230      */

231     public void removeTriggerFailureListener( TriggerFailureListener listener )
232     {
233         getTriggerFailureListeners().remove( listener );
234     }
235
236     /**
237      * Schedule a time based trigger.
238      * Note that if a TimeTrigger already has same name then it is removed.
239      *
240      * @param name the name of the trigger
241      * @param trigger the trigger
242      * @param target the target
243      */

244     public synchronized void addTrigger( final String JavaDoc name,
245                                          final TimeTrigger trigger,
246                                          final Target target )
247     {
248         try
249         {
250             removeTrigger( name );
251         }
252         catch( final NoSuchElementException JavaDoc nse )
253         {
254         }
255
256         final TimeScheduledEntry entry = new TimeScheduledEntry( name, trigger, target );
257         getEntryMap().put( name, entry );
258         final boolean added = rescheduleEntry( entry, false );
259
260         if( !added ) return;
261
262         try
263         {
264             if( entry == getPriorityQueue().peek() )
265             {
266                 notifyAll();
267             }
268         }
269         catch( final NoSuchElementException JavaDoc nse )
270         {
271             final String JavaDoc message =
272                 "Unexpected exception when peek() on priority queue for " +
273                 entry.getName();
274             getLogger().warn( message, nse );
275         }
276     }
277
278     /**
279      * Remove a scheduled trigger by name.
280      *
281      * @param name the name of the trigger
282      * @exception NoSuchElementException if no trigger exists with that name
283      */

284     public synchronized void removeTrigger( String JavaDoc name )
285         throws NoSuchElementException JavaDoc
286     {
287         //use the kill-o-matic against any entry with same name
288
final TimeScheduledEntry entry = getEntry( name );
289         entry.invalidate();
290         getEntryMap().remove( name );
291     }
292
293     /**
294      * Force a trigger time to be recalculated.
295      *
296      * @param name the name of the trigger
297      * @exception NoSuchElementException if no trigger exists with that name
298      */

299     public synchronized void resetTrigger( final String JavaDoc name )
300         throws NoSuchElementException JavaDoc
301     {
302         final TimeScheduledEntry entry = getEntry( name );
303         entry.getTimeTrigger().reset();
304         rescheduleEntry( entry, true );
305     }
306
307     // ----------------------------------------------------------------------
308
// Work Interface: MonitorableTimeSchedulerMBean
309
// ----------------------------------------------------------------------
310

311     /**
312      * Return a collection of the triggerable names.
313      * @return the collection
314      */

315     public synchronized Collection JavaDoc getEntries()
316     {
317         Collection JavaDoc coll = getEntryMap().keySet();
318         Vector JavaDoc retval = new Vector JavaDoc();
319         for( Iterator JavaDoc iterator = coll.iterator(); iterator.hasNext(); )
320         {
321             TimeScheduledEntry tse = (TimeScheduledEntry)getEntryMap().get( iterator.next() );
322             retval.add( tse.toString() );
323         }
324         return retval;
325     }
326
327     // ----------------------------------------------------------------------
328
// Helper methods
329
// ----------------------------------------------------------------------
330

331     /**
332      * Reschedule an entry.
333      * if clone is true then invalidate old version and create a new entry to
334      * insert into queue.
335      *
336      * @param timeEntry the entry
337      * @param clone true if new entry is to be created
338      * @return true if added to queue, false if not added
339      */

340     protected synchronized boolean rescheduleEntry( final TimeScheduledEntry timeEntry,
341                                                   final boolean clone )
342     {
343         TimeScheduledEntry entry = timeEntry;
344
345         if( clone )
346         {
347             entry = new TimeScheduledEntry( timeEntry.getName(),
348                                             timeEntry.getTimeTrigger(),
349                                             timeEntry.getTarget() );
350             timeEntry.invalidate();
351
352             // remove old refernce to the entry..so that next time
353
// somebody calls getEntry( name ), we will get the new valid entry.
354
getEntryMap().remove( timeEntry.getName() );
355             getEntryMap().put( timeEntry.getName(), entry );
356         }
357
358         //reschedule if appropriate
359
final long next = entry.getTimeTrigger().getTimeAfter( System.currentTimeMillis() );
360
361         if( 0 < next )
362         {
363             entry.setNextTime( next );
364             getPriorityQueue().insert( entry );
365
366             if( entry == getPriorityQueue().peek() )
367             {
368                 notify();
369             }
370
371             return true;
372         }
373         else
374         {
375             return false;
376         }
377     }
378
379     /**
380      * Retrieve entry from set.
381      *
382      * @param name the name of entry
383      * @return the entry
384      * @exception NoSuchElementException if no entry is found with that name
385      */

386     protected TimeScheduledEntry getEntry( final String JavaDoc name )
387         throws NoSuchElementException JavaDoc
388     {
389         //use the kill-o-matic against any entry with same name
390
final TimeScheduledEntry entry = (TimeScheduledEntry)getEntryMap().get( name );
391         if( null != entry )
392         {
393             return entry;
394         }
395         else
396         {
397             throw new NoSuchElementException JavaDoc();
398         }
399     }
400
401     /**
402      * Run entry in a separate thread and reschedule it.
403      *
404      * @param entry the entry to run
405      */

406     protected void runEntry( final TimeScheduledEntry entry )
407     {
408         final Runnable JavaDoc runnable = new Runnable JavaDoc()
409         {
410             public void run()
411             {
412                 doRunEntry( entry );
413                 // Stefan Seifert:
414
// rescheduleEntry( entry, false );
415
//
416
// and then don't reschedule at the end of runEntry
417
// this will ensure long-running events are
418
// queued
419
//
420
// LSD:
421
// that might break other apps. No-can-do.
422
}
423         };
424
425         //this should suck threads from a named pool
426
try
427         {
428             getThreadManager().getDefaultThreadPool().execute( runnable );
429         }
430         catch( final Exception JavaDoc e )
431         {
432             final String JavaDoc message = "Error executing trigger " + entry.getName();
433             getLogger().warn( message, e );
434         }
435         
436                 // reschedule entry
437
rescheduleEntry( entry, false );
438     }
439
440     /**
441      * Helper method delegated to to run in a separate thread.
442      *
443      * @param entry the entry to run
444      */

445     protected void doRunEntry( final TimeScheduledEntry entry )
446     {
447         try
448         {
449             entry.getTarget().targetTriggered( entry.getName() );
450         }
451         catch( final Error JavaDoc e )
452         {
453             final String JavaDoc message = "Error occured executing trigger " + entry.getName();
454             getLogger().error( message, e );
455             notifyFailedTriggers( e );
456
457         }
458         catch( final Exception JavaDoc e )
459         {
460             final String JavaDoc message = "Exception occured executing trigger " + entry.getName();
461             getLogger().warn( message, e );
462             notifyFailedTriggers( e );
463         }
464     }
465
466     /**
467      * Retrieve next valid entry. It will pop off any
468      * invalid entrys until the heap is empty or a valid entry
469      * is found.
470      *
471      * @return the next valid entry or null if none
472      */

473     protected synchronized TimeScheduledEntry getNextEntry()
474     {
475         TimeScheduledEntry entry =
476             (TimeScheduledEntry)getPriorityQueue().peek();
477
478         //if job has been invalidated then remove it and continue
479
while( !entry.isValid() )
480         {
481             getPriorityQueue().pop();
482
483             if( getPriorityQueue().isEmpty() )
484             {
485                 return null;
486             }
487
488             entry = (TimeScheduledEntry)getPriorityQueue().peek();
489         }
490
491         return entry;
492     }
493
494     protected void notifyFailedTriggers( Throwable JavaDoc t )
495     {
496         for( int i = 0; i < getTriggerFailureListeners().size(); i++ )
497         {
498             TriggerFailureListener triggerFailureListener = (TriggerFailureListener)m_triggerFailureListeners.get( i );
499             triggerFailureListener.triggerFailure( t );
500         }
501
502     }
503 }
504
505
Popular Tags