KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > ejb > containers > EJBTimerService


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23 package com.sun.ejb.containers;
24
25 import java.util.Date JavaDoc;
26 import java.util.Collection JavaDoc;
27 import java.util.Set JavaDoc;
28 import java.util.HashSet JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.Iterator JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.Map JavaDoc;
33 import java.util.Map.Entry;
34 import java.util.TimerTask JavaDoc;
35 import java.util.Properties JavaDoc;
36 import java.util.logging.Logger JavaDoc;
37 import java.util.logging.Level JavaDoc;
38
39 import java.text.DateFormat JavaDoc;
40 import java.text.SimpleDateFormat JavaDoc;
41
42 import java.io.File JavaDoc;
43 import java.io.FileWriter JavaDoc;
44 import java.io.FileReader JavaDoc;
45 import java.io.BufferedWriter JavaDoc;
46 import java.io.BufferedReader JavaDoc;
47 import java.io.PrintWriter JavaDoc;
48 import java.io.File JavaDoc;
49 import java.io.Serializable JavaDoc;
50
51 import com.sun.logging.LogDomains;
52 import com.sun.ejb.ContainerFactory;
53
54 import javax.ejb.Timer JavaDoc;
55 import javax.ejb.EJBException JavaDoc;
56 import javax.ejb.FinderException JavaDoc;
57 import javax.ejb.CreateException JavaDoc;
58 import javax.ejb.RemoveException JavaDoc;
59
60 import com.sun.enterprise.admin.monitor.callflow.Agent;
61 import com.sun.enterprise.admin.monitor.callflow.RequestType;
62 import com.sun.enterprise.Switch;
63 import com.sun.enterprise.deployment.*;
64
65 import com.sun.enterprise.server.ApplicationServer;
66 import com.sun.enterprise.instance.InstanceEnvironment;
67 import com.sun.enterprise.instance.AppsManager;
68 import com.sun.enterprise.instance.ServerManager;
69
70 import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
71 import com.sun.enterprise.server.ServerContext;
72 import com.sun.enterprise.config.serverbeans.EjbContainer;
73 import com.sun.enterprise.config.serverbeans.EjbTimerService;
74
75 import javax.transaction.TransactionManager JavaDoc;
76
77 /*
78  * EJBTimerService is the central controller of the EJB timer service.
79  * There is one instance of EJBTimerService per VM. All operations and
80  * state transitions on timers pass through EJB Timer Service. This
81  * reduces the overall complexity by encapsulating the timer logic
82  * within this class and keeping the supporting classes simple.
83  *
84  * @author Kenneth Saks
85  */

86 public class EJBTimerService
87 implements com.sun.ejb.spi.distributed.DistributedEJBTimerService
88 {
89     private long nextTimerIdMillis_ = 0;
90     private long nextTimerIdCounter_ = 0;
91     private String JavaDoc serverName_;
92     private String JavaDoc domainName_;
93
94     // @@@ Double-check that the individual server id, domain name,
95
// and cluster name cannot contain the TIMER_ID_SEP
96
// characters.
97

98     // separator between components that make up timer id and owner id
99
private static final String JavaDoc TIMER_ID_SEP = "@@";
100
101     // Owner id of the server instance in which we are currently running.
102
private String JavaDoc ownerIdOfThisServer_;
103
104     // A cache of timer info for all timers *owned* by this server instance.
105
private TimerCache timerCache_;
106
107     private TimerLocalHome timerLocalHome_;
108     private TimerMigrationLocalHome timerMigrationLocalHome_;
109     private boolean shutdown_;
110
111     // Total number of ejb components initialized as timed objects between the
112
// start and end of a single server instance. This value is used during
113
// restoreTimers() as an optimization to avoid initialization overhead in
114
// the common case that there are no applications with timed objects.
115
// It is NOT intended to be a consistent count of the current number of
116
// timed objects, so there is no need to decrement the number when a
117
// container is undeployed.
118
private long totalTimedObjectsInitialized_ = 0;
119
120     private static Logger JavaDoc logger = LogDomains.getLogger(LogDomains.EJB_LOGGER);
121
122     // Defaults for configurable timer service properties.
123

124     private static final long MINIMUM_DELIVERY_INTERVAL = 7000;
125     private static final int MAX_REDELIVERIES = 1;
126     private static final long REDELIVERY_INTERVAL = 5000;
127
128     private String JavaDoc appID;
129
130     // minimum amount of time between either a timer creation and its first
131
// expiration or between subsequent timer expirations.
132
private long minimumDeliveryInterval_ = MINIMUM_DELIVERY_INTERVAL;
133
134     // maximum number of times the container will attempt to retry a
135
// timer delivery before giving up.
136
private long maxRedeliveries_ = MAX_REDELIVERIES;
137
138     // amount of time the container waits between timer redelivery attempts.
139
private long redeliveryInterval_ = REDELIVERY_INTERVAL;
140
141     private static final String JavaDoc TIMER_SERVICE_FILE =
142         "__timer_service_shutdown__.dat";
143     private static final String JavaDoc TIMER_SERVICE_DOWNTIME_FORMAT =
144         "yyyy/MM/dd HH:mm:ss";
145
146     // This boolean value would be set in PE to be a default value of false.
147
// In case of EE the default value would be true. When set to true the
148
// timer service would have maximim consistency with performance degration.
149
private boolean performDBReadBeforeTimeout = false;
150     
151     private static final String JavaDoc strDBReadBeforeTimeout =
152         "com.sun.ejb.timer.ReadDBBeforeTimeout";
153     private boolean foundSysPropDBReadBeforeTimeout = false;
154
155     public EJBTimerService(String JavaDoc appID, TimerLocalHome timerLocalHome,
156                            TimerMigrationLocalHome timerMigrationLocalHome)
157     {
158         timerLocalHome_ = timerLocalHome;
159         timerMigrationLocalHome_ = timerMigrationLocalHome;
160         timerCache_ = new TimerCache();
161         shutdown_ = false;
162         this.appID = appID;
163
164         domainName_ = ServerManager.instance().getDomainName();
165         InstanceEnvironment server =
166             ApplicationServer.getServerContext().getInstanceEnvironment();
167         serverName_ = server.getName();
168
169         initProperties();
170     }
171
172     private void initProperties() {
173
174         try {
175             
176             // Check for property settings from domain.xml
177
ServerContext sc = ApplicationServer.getServerContext();
178             EjbContainer ejbc = ServerBeansFactory.
179                 getConfigBean(sc.getConfigContext()).getEjbContainer();
180             EjbTimerService ejbt = ejbc.getEjbTimerService();
181
182             if( ejbt != null ) {
183
184                 String JavaDoc valString = ejbt.getMinimumDeliveryIntervalInMillis();
185                 long val = (valString != null) ?
186                     Long.parseLong(valString) : -1;
187                     
188                 if( val > 0 ) {
189                     minimumDeliveryInterval_ = val;
190                 }
191
192                 valString = ejbt.getMaxRedeliveries();
193                 val = (valString != null) ? Long.parseLong(valString) : -1;
194                 // EJB 2.1 specification minimum is 1
195
if( val > 0 ) {
196                     maxRedeliveries_ = val;
197                 }
198
199                 valString = ejbt.getRedeliveryIntervalInternalInMillis();
200                 val = (valString != null) ? Long.parseLong(valString) : -1;
201                 if( val > 0 ) {
202                     redeliveryInterval_ = val;
203                 }
204
205                 // If the system property com.sun.ejb.timer.ReadDBBeforeTimeout
206
// is defined by the user use that the value of the flag
207
// performDBReadBeforeTimeout
208
foundSysPropDBReadBeforeTimeout =
209                     getDBReadBeforeTimeoutProperty();
210
211                 // The default value for ReadDBBeforeTimeout in case of PE
212
// is false. For SE/EE the correct default would set when the
213
// EJBLifecyleImpl gets created as part of the EE lifecycle module
214
setPerformDBReadBeforeTimeout( false );
215             }
216
217             // Compose owner id for all timers created with this
218
// server instance.
219
InstanceEnvironment server = sc.getInstanceEnvironment();
220             String JavaDoc serverName = server.getName();
221             ownerIdOfThisServer_ = serverName;
222
223         } catch(Exception JavaDoc e) {
224             logger.log(Level.FINE, "Exception converting timer service " +
225                "domain.xml properties. Defaults will be used instead.", e);
226         }
227
228         logger.log(Level.FINE, "EJB Timer Service properties : " +
229                    "min delivery interval = " + minimumDeliveryInterval_ +
230                    "\nmax redeliveries = " + maxRedeliveries_ +
231                    "\nredelivery interval = " + redeliveryInterval_);
232     }
233
234     synchronized void timedObjectCount() {
235         totalTimedObjectsInitialized_++;
236     }
237
238     /**
239      * Return the ownerId of the server instance in
240      * which we are running.
241      */

242     String JavaDoc getOwnerIdOfThisServer() {
243         return ownerIdOfThisServer_;
244     }
245
246     /**
247      *--------------------------------------------------------------
248      * Methods to be implemented for DistributedEJBTimerService
249      *--------------------------------------------------------------
250      */

251
252     /**
253      * Create EJBException using the exception that is passed in
254      */

255     private EJBException JavaDoc createEJBException( Exception JavaDoc ex ) {
256         EJBException JavaDoc ejbEx = new EJBException JavaDoc();
257         ejbEx.initCause(ex);
258         return ejbEx;
259     }
260
261     /**
262      * Provide a count of timers owned by each server
263      */

264     public String JavaDoc[] listTimers( String JavaDoc[] serverIds ) {
265         String JavaDoc[] totalTimers = new String JavaDoc[ serverIds.length ];
266         try {
267             for ( int i = 0; i < serverIds.length; i++ ) {
268                 totalTimers[i] = new String JavaDoc(
269                     new Integer JavaDoc(
270                         timerLocalHome_.selectCountAllTimersOwnedBy(
271                                 serverIds[i] )).toString());
272             }
273         } catch( Exception JavaDoc ex ) {
274             logger.log( Level.SEVERE, "Exception in listTimers() : " , ex );
275
276             //Propogate any exceptions caught
277
EJBException JavaDoc ejbEx = createEJBException( ex );
278             throw ejbEx;
279         }
280         return totalTimers;
281     }
282
283     /**
284      * Take ownership of another server's timers.
285      */

286     public int migrateTimers(String JavaDoc fromOwnerId) {
287
288         String JavaDoc ownerIdOfThisServer = getOwnerIdOfThisServer();
289
290         if( fromOwnerId.equals(ownerIdOfThisServer) ) {
291             /// Error. The server from which timers are being
292
// migrated should never be up and running OR receive this
293
// notification.
294
logger.log(Level.WARNING, "Attempt to migrate timers from " +
295                         "an active server instance " + ownerIdOfThisServer);
296             throw new IllegalStateException JavaDoc("Attempt to migrate timers from " +
297                                             " an active server instance " +
298                                             ownerIdOfThisServer);
299         }
300
301         logger.log(Level.INFO, "Beginning timer migration process from " +
302                    "owner " + fromOwnerId + " to " + ownerIdOfThisServer);
303
304         TransactionManager JavaDoc tm = Switch.getSwitch().getTransactionManager();
305
306         Set JavaDoc toRestore = new HashSet JavaDoc();
307
308         try {
309                               
310             tm.begin();
311
312             // The timer objects we'll use to set the new owner id come
313
// from TimerMigrationBean
314
Set JavaDoc toMigrate = timerMigrationLocalHome_.
315                 selectAllTimersOwnedBy(fromOwnerId);
316
317             // The timer objects we'll use for restoring come from TimerBean
318
// The same EJB QL query is used for selectAllTimersOwnedBy(owner)
319
toRestore = timerLocalHome_.
320                 selectAllTimersOwnedBy(fromOwnerId);
321
322             for(Iterator JavaDoc iter = toMigrate.iterator(); iter.hasNext();) {
323                 TimerMigrationLocal next = (TimerMigrationLocal) iter.next();
324                 next.setOwnerId(ownerIdOfThisServer);
325             }
326
327             tm.commit();
328
329         } catch(Exception JavaDoc e) {
330             // Don't attempt to restore any timers since an error has
331
// occurred. This could be the expected result in the case that
332
// multiple server instances attempted the migration at the same
333
// time.
334
toRestore = new HashSet JavaDoc();
335
336             logger.log(Level.FINE, "timer migration error", e);
337
338             try {
339                 tm.rollback();
340             } catch(Exception JavaDoc re) {
341                 logger.log(Level.FINE, "timer migration rollback error", re);
342             }
343
344             //Propagate the exception caught
345
EJBException JavaDoc ejbEx = createEJBException( e );
346             throw ejbEx;
347         }
348
349     int totalTimersMigrated = toRestore.size();
350
351         if( toRestore.size() > 0 ) {
352
353             boolean success = false;
354             try {
355                 
356                 logger.log(Level.INFO, "Timer migration phase 1 complete. " +
357                            "Changed ownership of " + toRestore.size() +
358                            " timers. Now reactivating timers...");
359
360                 tm.begin();
361                 
362                 _restoreTimers(toRestore);
363                 success = true;
364                 
365             } catch(Exception JavaDoc e) {
366
367                 logger.log(Level.FINE, "timer restoration error", e);
368
369                 //Propogate any exceptions caught as part of the transaction
370
EJBException JavaDoc ejbEx = createEJBException( e );
371                 throw ejbEx;
372
373             } finally {
374                 // We're not modifying any state in this tx so no harm in
375
// always committing.
376
try {
377                     tm.commit();
378                 } catch(Exception JavaDoc re) {
379                     logger.log(Level.FINE, "timer migration error", re);
380
381                     if( success ) {
382                         //Propogate any exceptions caught when trying to commit
383
//the transaction
384
EJBException JavaDoc ejbEx = createEJBException( re );
385                         throw ejbEx;
386                     }
387                 }
388             }
389         } else {
390             logger.log(Level.INFO, fromOwnerId + " has 0 timers in need " +
391                        "of migration");
392         }
393         
394         return totalTimersMigrated;
395
396     } //migrateTimers()
397

398     public void setPerformDBReadBeforeTimeout( boolean defaultDBReadValue ) {
399
400         // If the system property com.sun.ejb.timer.ReadDBBeforeTimeout
401
// has been defined by the user then use that value else use the default
402
if ( !foundSysPropDBReadBeforeTimeout ) {
403             performDBReadBeforeTimeout = defaultDBReadValue;
404
405             if( logger.isLoggable(Level.FINE) ) {
406                 logger.log(Level.FINE, "EJB Timer Service property : " +
407                            "\nread DB before timeout delivery = " +
408                            performDBReadBeforeTimeout);
409             }
410
411         }
412     }
413
414     /**
415      * Check to see if the user has defined a System property to specify if
416      * we need to check the timer table in the database and confirm that the
417      * timer is valid before delivering the ejbTimeout() for that timer.
418      *
419      * In case of PE - the default value is false
420      * and for SE/EE - the default value is true
421      *
422      * But in all cases (PE/SE/EE) the user can set the System property
423      * "READ_DB_BEFORE_EJBTIMEOUT" to change the behaviour
424      */

425     private boolean getDBReadBeforeTimeoutProperty() {
426
427         boolean result = false;
428         try{
429             Properties JavaDoc props = System.getProperties();
430             String JavaDoc str=props.getProperty( strDBReadBeforeTimeout );
431             if( null != str) {
432         str = str.toLowerCase();
433                 performDBReadBeforeTimeout = Boolean.valueOf(str).booleanValue();
434
435                 if( logger.isLoggable(Level.FINE) ) {
436                     logger.log(Level.FINE, "EJB Timer Service property : " +
437                                "\nread DB before timeout delivery = " +
438                                performDBReadBeforeTimeout);
439                 }
440
441                 result = true;
442             }
443         } catch(Exception JavaDoc e) {
444             logger.log(Level.INFO,
445                 "ContainerFactoryImpl.getDebugMonitoringDetails(), " +
446                 " Exception when trying to " +
447                 "get the System properties - ", e);
448         }
449         return result;
450     }
451
452     /**
453      * Called at server startup *after* user apps have been re-activated
454      * to restart any active EJB timers.
455      */

456     void restoreTimers() throws Exception JavaDoc {
457
458         // Optimization. Skip timer restoration if there aren't any
459
// applications with timed objects deployed.
460
if( totalTimedObjectsInitialized_ == 0 ) {
461             return;
462         }
463
464         TransactionManager JavaDoc tm = Switch.getSwitch().getTransactionManager();
465         Set JavaDoc allActiveTimers = new HashSet JavaDoc();
466         try {
467             // create a tx in which to do database access for all timers
468
// needing restoration. This gives us better performance that
469
// doing individual transactions per timer.
470
tm.begin();
471
472             // This operation can take a while, since in some configurations
473
// this will be the first time the connection to the database
474
// is initialized. In addition, there's an initialization
475
// cost to generating the SQL for the underlying
476
// ejbql queries the first time any TimerBean query is called.
477
allActiveTimers =
478                 timerLocalHome_.selectAllActiveTimersOwnedByThisServer();
479
480             _restoreTimers(allActiveTimers);
481         } catch(Exception JavaDoc e) {
482
483             // Problem accessing timer service so disable it.
484
ContainerFactoryImpl cf = (ContainerFactoryImpl)
485                 Switch.getSwitch().getContainerFactory();
486             cf.setEJBTimerService(null);
487
488             logger.log(Level.WARNING, "ejb.timer_service_init_error", e);
489
490             // No need to propagate exception. EJB Timer Service is disabled
491
// but that won't affect the rest of the EJB container services.
492
return;
493
494         } finally {
495             // try to commit regardless of success or failure.
496
try {
497                 tm.commit();
498             } catch(Exception JavaDoc e) {
499                 logger.log(Level.WARNING, "ejb.timer_service_init_error", e);
500             }
501         }
502     }
503
504     /**
505      * The portion of timer restoration that deals with registering the
506      * JDK timer tasks and checking for missed expirations.
507      */

508     private void _restoreTimers(Set JavaDoc timersEligibleForRestoration) {
509                       
510         // Do timer restoration in two passes. The first pass updates
511
// the timer cache with each timer. The second pass schedules
512
// the JDK timer tasks.
513

514         Map JavaDoc timersToRestore = new HashMap JavaDoc();
515
516         for(Iterator JavaDoc iter = timersEligibleForRestoration.iterator();
517             iter.hasNext();) {
518
519             TimerLocal next = (TimerLocal) iter.next();
520             long containerId = next.getContainerId();
521
522             // Timer might refer to an obsolete container.
523
BaseContainer container = getContainer(containerId);
524             if( container != null ) {
525                 
526                 TimerPrimaryKey timerId = (TimerPrimaryKey)next.getPrimaryKey();
527                 Date JavaDoc initialExpiration = next.getInitialExpiration();
528                
529                 // Create an instance of RuntimeTimerState.
530

531                 // Only access timedObjectPrimaryKey if timed object is
532
// an entity bean. That allows us to lazily load the underlying
533
// blob for stateless session and message-driven bean timers.
534
Object JavaDoc timedObjectPrimaryKey = null;
535                 if( container instanceof EntityContainer ) {
536                     timedObjectPrimaryKey = next.getTimedObjectPrimaryKey();
537                 }
538
539                 RuntimeTimerState timerState = new RuntimeTimerState
540                     (timerId, initialExpiration,
541                      next.getIntervalDuration(), container,
542                      timedObjectPrimaryKey);
543
544                 timerCache_.addTimer(timerId, timerState);
545                 
546                 // If a single-action timer is still in the database it never
547
// successfully delivered, so always reschedule a timer task
548
// for it. For periodic timers, we use the last known
549
// expiration time to decide whether we need to fire one
550
// ejbTimeout to make up for any missed ones.
551
Date JavaDoc expirationTime = initialExpiration;
552                 Date JavaDoc now = new Date JavaDoc();
553
554                 if( timerState.isPeriodic() ) {
555                     // lastExpiration time, or null if we either aren't
556
// tracking last expiration or an expiration hasn't
557
// occurred yet for this timer.
558
Date JavaDoc lastExpiration = next.getLastExpiration();
559
560                     // @@@ need to handle case where last expiration time
561
// is not stored in database. This will be the case
562
// when we add configuration for update-db-on-delivery.
563
// However, for now assume we do update the db on each
564
// ejbTimeout. Therefore, if (lastExpirationTime == null),
565
// it means the timer didn't successfully complete any
566
// timer expirations.
567

568                     if( (lastExpiration == null) &&
569                         now.after(initialExpiration) ) {
570                         
571                         // This timer didn't even expire one time.
572
logger.log(Level.INFO,
573                                    "Rescheduling missed expiration for " +
574                                    "periodic timer " +
575                                    timerState + ". Timer expirations should " +
576                                    " have been delivered starting at " +
577                                    initialExpiration);
578
579                         // keep expiration time at initialExpiration. That
580
// will force an ejbTimeout almost immediately. After
581
// that the timer will return to fixed rate expiration.
582

583                     } else if ( (lastExpiration != null) &&
584                                 ( (now.getTime() - lastExpiration.getTime()
585                                    > next.getIntervalDuration()) ) ) {
586                         
587                         logger.log(Level.INFO,
588                                    "Rescheduling missed expiration for " +
589                                    "periodic timer " +
590                                    timerState + ". Last timer expiration " +
591                                    "occurred at " + lastExpiration);
592                         
593                         // Timer expired at least once and at least one
594
// missed expiration has occurred.
595

596                         // keep expiration time at initialExpiration. That
597
// will force an ejbTimeout almost immediately. After
598
// that the timer will return to fixed rate expiration.
599

600                     } else {
601
602                         // In this case, at least one expiration has occurred
603
// but that was less than one period ago so there were
604
// no missed expirations.
605
expirationTime =
606                             calcNextFixedRateExpiration(timerState);
607                     }
608                     
609                 } else { // single-action timer
610

611                     if( now.after(initialExpiration) ) {
612                         logger.log(Level.INFO,
613                                    "Rescheduling missed expiration for " +
614                                    "single-action timer " +
615                                    timerState + ". Timer expiration should " +
616                                    " have been delivered at " +
617                                    initialExpiration);
618                     }
619                 }
620
621                 timersToRestore.put(timerState, expirationTime);
622
623             } else {
624                 // Timed object's container no longer exists.
625
try {
626                     next.remove();
627                 } catch(RemoveException JavaDoc e) {
628                     logger.log(Level.FINE,
629                         "Removing timer " + next.getPrimaryKey() +
630                                " for unknown container " + containerId, e);
631                 }
632             }
633         } // End -- for each active timer
634

635         for(Iterator JavaDoc entries = timersToRestore.entrySet().iterator();
636             entries.hasNext(); ) {
637             Map.Entry JavaDoc next = (Map.Entry JavaDoc) entries.next();
638             RuntimeTimerState nextTimer = (RuntimeTimerState) next.getKey();
639             TimerPrimaryKey timerId = nextTimer.getTimerId();
640             Date JavaDoc expiration = (Date JavaDoc) next.getValue();
641             scheduleTask(timerId, expiration);
642             logger.log(Level.FINE,
643                        "EJBTimerService.restoreTimers(), scheduling timer " +
644                        nextTimer);
645         }
646
647         logger.log(Level.FINE, "DONE EJBTimerService.restoreTimers()");
648     }
649
650     void shutdown() {
651         // Set flag to prevent any new timer expirations.
652
shutdown_ = true;
653     }
654
655     /**
656      * Cancel all timers associated with a particular entity bean
657      * identity. This is typically called when an entity bean is
658      * removed. Note that this action falls under the normal EJB
659      * Timer removal semantics, which means it can be rolled back
660      * if the transaction rolls back.
661      */

662     void cancelEntityBeanTimers(long containerId, Object JavaDoc primaryKey) {
663         try {
664             // Get *all* timers for this entity bean identity. This includes
665
// even timers *not* owned by this server instance, but that
666
// are associated with the same entity bean and primary key.
667
Collection JavaDoc timers = getTimers(containerId, primaryKey);
668             if( logger.isLoggable(Level.FINE) ) {
669                 if( timers.isEmpty() ) {
670                     logger.log(Level.FINE, "0 cancelEntityBeanTimers for " +
671                                containerId + ", " + primaryKey);
672                 }
673             }
674             for(Iterator JavaDoc iter = timers.iterator(); iter.hasNext();) {
675                 TimerLocal next = (TimerLocal) iter.next();
676                 try {
677                     cancelTimer(next);
678                 } catch(Exception JavaDoc e) {
679                     logger.log(Level.WARNING, "ejb.cancel_entity_timer",
680                                new Object JavaDoc[] { next.getPrimaryKey() });
681                     logger.log(Level.WARNING, "", e);
682                 }
683             }
684         } catch(Exception JavaDoc e) {
685             logger.log(Level.WARNING, "ejb.cancel_entity_timers",
686                        new Object JavaDoc[] { new Long JavaDoc(containerId), primaryKey });
687             logger.log(Level.WARNING, "", e);
688         }
689     }
690
691     /**
692      * Destroy all timers associated with a particular ejb container
693      * and owned by this server instance.
694      * This is typically called when an ejb is undeployed. It expunges
695      * all timers whose timed object matches the given container. In
696      * the case of an entity bean container, all timers associated with
697      * any of that container's entity bean identities will be destroyed.
698      * This action *can not* be rolled back.
699      */

700     void destroyTimers(long containerId) {
701         Set JavaDoc timers = null;
702
703         TransactionManager JavaDoc tm = Switch.getSwitch().getTransactionManager();
704
705         try {
706             
707             // create a tx in which to do database access for all timers
708
// that need to be deleted. This gives us better performance that
709
// doing individual transactions per timer.
710
tm.begin();
711             
712             // Get *all* timers for this ejb. Since the app is being undeployed
713
// any server instance can delete all the timers for the same ejb.
714
// Whichever one gets there first will actually do the delete.
715
timers = timerLocalHome_.selectTimersByContainer(containerId);
716
717             for(Iterator JavaDoc iter = timers.iterator(); iter.hasNext();) {
718                 TimerLocal next = (TimerLocal) iter.next();
719                 TimerPrimaryKey nextTimerId = null;
720                 RuntimeTimerState nextTimerState = null;
721                 try {
722                     nextTimerId = (TimerPrimaryKey) next.getPrimaryKey();
723                     nextTimerState = getTimerState(nextTimerId);
724                     if( nextTimerState != null ) {
725                         synchronized(nextTimerState) {
726                             if( nextTimerState.isScheduled() ) {
727                                 EJBTimerTask timerTask =
728                                     nextTimerState.getCurrentTimerTask();
729                                 timerTask.cancel();
730                             }
731                         }
732                     }
733                     next.remove();
734                 } catch(Exception JavaDoc e) {
735                     logger.log(Level.WARNING, "ejb.destroy_timer_error",
736                                new Object JavaDoc[] { nextTimerId });
737                     logger.log(Level.WARNING, "", e);
738                 } finally {
739                     if( nextTimerState != null ) {
740                         timerCache_.removeTimer(nextTimerId);
741                     }
742                 }
743             }
744
745         } catch(Exception JavaDoc ex) {
746             logger.log(Level.WARNING, "destroy_timers_error",
747                        new Object JavaDoc[] { new Long JavaDoc(containerId) });
748             logger.log(Level.WARNING, "", ex);
749             return;
750         } finally {
751             try {
752                 tm.commit();
753             } catch(Exception JavaDoc e) {
754                 // Most likely caused by two or more server instances trying
755
// to delete the timers for the same ejb at the same time.
756
logger.log(Level.WARNING, "destroy_timers_error",
757                            new Object JavaDoc[] { new Long JavaDoc(containerId) });
758                 logger.log(Level.WARNING, "", e);
759             }
760         }
761
762         return;
763     }
764
765     void rescheduleTask(TimerPrimaryKey timerId, Date JavaDoc expiration) {
766         scheduleTask(timerId, expiration, true);
767     }
768
769     void scheduleTask(TimerPrimaryKey timerId, Date JavaDoc expiration) {
770         scheduleTask(timerId, expiration, false);
771     }
772
773     void scheduleTask(TimerPrimaryKey timerId, Date JavaDoc expiration,
774                       boolean rescheduled) {
775     
776         RuntimeTimerState timerState = getTimerState(timerId);
777
778         if( timerState != null ) {
779             synchronized(timerState) {
780
781                 Date JavaDoc timerExpiration = expiration;
782
783                 if( !rescheduled ) {
784                     // Guard against very small timer intervals. The EJB Timer
785
// service is defined in units of milliseconds, but it is
786
// intended for coarse-grained events. Very small timer
787
// intervals (e.g. 1 millisecond) are likely to overload
788
// the server, so compensate by adjusting to a configurable
789
// minimum interval.
790
Date JavaDoc cutoff = new Date JavaDoc(new Date JavaDoc().getTime() +
791                                            getMinimumDeliveryInterval());
792                     if( expiration.before(cutoff) ) {
793                         timerExpiration = cutoff;
794                     }
795                 }
796
797                 EJBTimerTask timerTask =
798                     new EJBTimerTask(timerExpiration, timerId, this);
799                 if( logger.isLoggable(Level.FINE) ) {
800                     logger.log(Level.FINE, (rescheduled ? "RE-" : "") +
801                                "Scheduling " + timerState +
802                                " for timeout at " + timerExpiration);
803                 }
804                 if( rescheduled ) {
805                     timerState.rescheduled(timerTask);
806                 } else {
807                     timerState.scheduled(timerTask);
808                 }
809
810                 java.util.Timer JavaDoc jdkTimer = Switch.getSwitch().getTimer();
811                 jdkTimer.schedule(timerTask, timerExpiration);
812             }
813         } else {
814             
815             logger.log(Level.FINE, "No timer state found for " +
816                        (rescheduled ? "RE-schedule" : "schedule") +
817                        " request of " + timerId +
818                        " for timeout at " + expiration);
819         }
820     }
821
822
823     /**
824      * Called from TimerBean to cancel the next scheduled expiration
825      * for a timer.
826      * @return (initialExpiration time) if state is CREATED or
827      * (time that expiration would have occurred) if state=SCHEDULED or
828      * (null) if state is BEING_DELIVERED or timer id not found
829      */

830     Date JavaDoc cancelTask(TimerPrimaryKey timerId) {
831        
832         Date JavaDoc timeout = null;
833
834         RuntimeTimerState timerState = getTimerState(timerId);
835         if( timerState != null ) {
836             synchronized(timerState) {
837
838                 if( timerState.isCreated() ) {
839                     timeout = timerState.getInitialExpiration();
840                 } else if( timerState.isScheduled() ) {
841                     EJBTimerTask timerTask = timerState.getCurrentTimerTask();
842                     timeout = timerTask.getTimeout();
843                     timerTask.cancel();
844                 }
845                 timerState.cancelled();
846
847             }
848         } else {
849             logger.log(Level.FINE, "No timer state found for " +
850                        "cancelTask request of " + timerId);
851         }
852
853         return timeout;
854     }
855
856     /**
857      * Called from TimerBean in case where Timer is cancelled from within
858      * its own ejbTimeout method and then rolled back.
859      */

860     void restoreTaskToDelivered(TimerPrimaryKey timerId) {
861         
862         RuntimeTimerState timerState = getTimerState(timerId);
863         if( timerState != null ) {
864             synchronized(timerState) {
865                 timerState.restoredToDelivered();
866             }
867             if( logger.isLoggable(Level.FINE) ) {
868                 logger.log(Level.FINE, "Restoring " + timerId +
869                    " to delivered state after it was cancelled and " +
870                    " rolled back from within its own ejbTimeout method");
871             }
872         } else {
873             logger.log(Level.FINE, "No timer state found for " +
874                        "restoreTaskToDelivered request of " + timerId);
875         }
876     }
877
878     void expungeTimer(TimerPrimaryKey timerId) {
879         // Expunge timer from timer cache without removing timer associated
880
// timer bean.
881
expungeTimer(timerId, false);
882     }
883
884
885     private Date JavaDoc calcInitialFixedRateExpiration(long timerServiceWentDownAt,
886             RuntimeTimerState timerState)
887     {
888         if (!timerState.isPeriodic()) {
889             throw new IllegalStateException JavaDoc();
890         }
891         Date JavaDoc now = new Date JavaDoc();
892
893         long nowMillis = now.getTime();
894         long initialExpiration = timerState.getInitialExpiration().getTime();
895
896         long now2initialDiff = nowMillis - initialExpiration;
897         long count = now2initialDiff / timerState.getIntervalDuration();
898         long previousExpiration =
899             initialExpiration + (count * timerState.getIntervalDuration());
900
901         if ((previousExpiration >= timerServiceWentDownAt)
902                 && (previousExpiration <= nowMillis))
903         {
904             //We certainly missed this one while the server was down
905
logger.log(Level.INFO, "ejb.deliver_missed_timer",
906                        new Object JavaDoc[] { timerState.getTimerId(),
907                                       new Date JavaDoc(previousExpiration) });
908             return now;
909         } else {
910             //Calculate the new expiration time
911
return calcNextFixedRateExpiration(timerState);
912         }
913
914     }
915
916     private Date JavaDoc calcNextFixedRateExpiration(RuntimeTimerState timerState) {
917
918         if( !timerState.isPeriodic() ) {
919             throw new IllegalStateException JavaDoc("Timer " + timerState + " is " +
920                                             "not a periodic timer");
921         }
922
923         Date JavaDoc initialExpiration = timerState.getInitialExpiration();
924         long intervalDuration = timerState.getIntervalDuration();
925
926         return calcNextFixedRateExpiration(initialExpiration, intervalDuration);
927     }
928     
929     private Date JavaDoc calcNextFixedRateExpiration(Date JavaDoc initialExpiration,
930                                              long intervalDuration) {
931
932         Date JavaDoc now = new Date JavaDoc();
933         long nowMillis = now.getTime();
934
935         // In simplest case, initial expiration hasn't even occurred yet.
936
Date JavaDoc nextExpirationTime = initialExpiration;
937         
938         if( now.after(initialExpiration) ) {
939             long timeSinceInitialExpire =
940                 (nowMillis - initialExpiration.getTime());
941                                 
942             // number of time intervals since initial expiration.
943
// intervalDuration is guaranteed to be >0 since this is a
944
// periodic timer.
945
long numIntervals =
946                 (timeSinceInitialExpire / intervalDuration);
947             
948             // Increment the number of intervals and multiply by the interval
949
// duration to calculate the next fixed-rate boundary.
950
nextExpirationTime = new Date JavaDoc(initialExpiration.getTime() +
951                 ((numIntervals + 1) * intervalDuration));
952         }
953
954         return nextExpirationTime;
955     }
956
957     /**
958      * Remove all traces of a timer. This should be written defensively
959      * so that if expunge is called multiple times for the same timer id,
960      * the second, third, fourth, etc. calls will not cause exceptions.
961      */

962     private void expungeTimer(TimerPrimaryKey timerId,
963                               boolean removeTimerBean) {
964         // First remove timer bean. Don't update cache until
965
// afterwards, since accessing of timer bean might require
966
// access to timer state(e.g. timer application classloader)
967
if( removeTimerBean ) {
968             removeTimerBean(timerId);
969         }
970         timerCache_.removeTimer(timerId);
971     }
972
973     /**
974      * @param primaryKey can be null if timed object is not an entity bean.
975      * @return Primary key of newly created timer
976      */

977     TimerPrimaryKey createTimer(long containerId, Object JavaDoc timedObjectPrimaryKey,
978                                 long initialDuration, long intervalDuration,
979                                 Serializable JavaDoc info) throws CreateException JavaDoc {
980
981         Date JavaDoc now = new Date JavaDoc();
982
983         Date JavaDoc initialExpiration = new Date JavaDoc(now.getTime() + initialDuration);
984
985         return createTimer(containerId, timedObjectPrimaryKey,
986                            initialExpiration, intervalDuration, info);
987     }
988
989     /**
990      * @param primaryKey can be null if timed object is not an entity bean.
991      * @return Primary key of newly created timer
992      */

993     TimerPrimaryKey createTimer(long containerId, Object JavaDoc timedObjectPrimaryKey,
994                                 Date JavaDoc initialExpiration, long intervalDuration,
995                                 Serializable JavaDoc info) throws CreateException JavaDoc {
996
997         BaseContainer container = getContainer(containerId);
998         if( container == null ) {
999             throw new CreateException JavaDoc("invalid container id " + containerId +
1000                                      " in createTimer request");
1001        }
1002        
1003        Class JavaDoc ejbClass = container.getEJBClass();
1004        if( !container.isTimedObject() ) {
1005            throw new CreateException JavaDoc
1006                ("Attempt to create an EJB Timer from a bean that is " +
1007                 "not a Timed Object. EJB class " + ejbClass +
1008                 " must implement javax.ejb.TimedObject or " +
1009                 " annotation a timeout method with @Timeout");
1010        }
1011
1012        TimerPrimaryKey timerId = new TimerPrimaryKey(getNextTimerId());
1013
1014        RuntimeTimerState timerState =
1015            new RuntimeTimerState(timerId, initialExpiration,
1016                                  intervalDuration, container,
1017                                  timedObjectPrimaryKey);
1018
1019        synchronized(timerState) {
1020            // Add timer entry before calling TimerBean.create, since
1021
// create() actions might call back on EJBTimerService and
1022
// need access to timer cache.
1023
timerCache_.addTimer(timerId, timerState);
1024            try {
1025                timerLocalHome_.create(timerId.getTimerId(), containerId,
1026                                       ownerIdOfThisServer_,
1027                                       timedObjectPrimaryKey,
1028                                       initialExpiration, intervalDuration,
1029                                       info);
1030            } catch(Exception JavaDoc e) {
1031                logger.log(Level.SEVERE, "ejb.create_timer_failure",
1032                           new Object JavaDoc[] { new Long JavaDoc(containerId),
1033                                          timedObjectPrimaryKey,
1034                                          info });
1035                logger.log(Level.SEVERE, "", e);
1036                // Since timer was never created, remove it from cache.
1037
timerCache_.removeTimer(timerId);
1038                if( e instanceof CreateException JavaDoc ) {
1039                    throw ((CreateException JavaDoc)e);
1040                } else {
1041                    EJBException JavaDoc ejbEx = new EJBException JavaDoc();
1042                    ejbEx.initCause(e);
1043                    throw ejbEx;
1044                }
1045            }
1046        }
1047
1048        return timerId;
1049    }
1050
1051    /**
1052     * Use database query to retrieve all active timers. Results must
1053     * be transactionally consistent. E.g., a client calling
1054     * getTimers within a transaction where a timer has been
1055     * created but not committed "sees" the timer but a client
1056     * in a different transaction doesn't.
1057     *
1058     * @param primaryKey can be null if not entity bean
1059     *
1060     * @return Collection of TimerLocal objects.
1061     */

1062    private Collection JavaDoc getTimers(long containerId,
1063                                 Object JavaDoc timedObjectPrimaryKey)
1064        throws FinderException JavaDoc {
1065
1066        // The results should include all timers for the given ejb
1067
// and/or primary key, including timers owned by other server instances.
1068

1069        // @@@ Might want to consider cases where we can use
1070
// timer cache to avoid some database access in PE/SE, or
1071
// even in EE with the appropriate consistency tradeoff.
1072

1073        Collection JavaDoc activeTimers =
1074            timerLocalHome_.selectActiveTimersByContainer(containerId);
1075        
1076        Collection JavaDoc timersForTimedObject = activeTimers;
1077
1078        if( timedObjectPrimaryKey != null ) {
1079                                  
1080            // Database query itself can't do equality check on primary
1081
// key of timed object so perform check ourselves.
1082

1083            timersForTimedObject = new HashSet JavaDoc();
1084            
1085            for(Iterator JavaDoc iter = activeTimers.iterator(); iter.hasNext();) {
1086                TimerLocal next = (TimerLocal) iter.next();
1087               
1088                Object JavaDoc nextTimedObjectPrimaryKey =
1089                    next.getTimedObjectPrimaryKey();
1090                if( nextTimedObjectPrimaryKey.equals(timedObjectPrimaryKey) ) {
1091                    timersForTimedObject.add(next);
1092                }
1093            }
1094        }
1095
1096        return timersForTimedObject;
1097    }
1098
1099    /**
1100     * Use database query to retrieve the timer ids of all active
1101     * timers. Results must be transactionally consistent. E.g.,
1102     * a client calling getTimerIds within a transaction where a
1103     * timer has been created but not committed "sees" the timer
1104     * but a client in a different transaction doesn't. Called by
1105     * EJBTimerServiceWrapper when caller calls getTimers.
1106     *
1107     * @param primaryKey can be null if not entity bean
1108     * @return Collection of Timer Ids.
1109     */

1110    Collection JavaDoc getTimerIds(long containerId, Object JavaDoc timedObjectPrimaryKey)
1111        throws FinderException JavaDoc {
1112
1113        // The results should include all timers for the given ejb
1114
// and/or primary key, including timers owned by other server instances.
1115

1116        // @@@ Might want to consider cases where we can use
1117
// timer cache to avoid some database access in PE/SE, or
1118
// even in EE with the appropriate consistency tradeoff.
1119

1120        Collection JavaDoc timerIdsForTimedObject = new HashSet JavaDoc();
1121
1122        if( timedObjectPrimaryKey == null ) {
1123
1124            timerIdsForTimedObject =
1125                timerLocalHome_.selectActiveTimerIdsByContainer(containerId);
1126
1127        } else {
1128                                  
1129            // Database query itself can't do equality check on primary
1130
// key of timed object so perform check ourselves.
1131

1132            Collection JavaDoc timersForTimedObject = getTimers(containerId,
1133                                                        timedObjectPrimaryKey);
1134
1135            timerIdsForTimedObject = new HashSet JavaDoc();
1136            
1137            for(Iterator JavaDoc iter = timersForTimedObject.iterator();
1138                iter.hasNext();) {
1139                TimerLocal next = (TimerLocal) iter.next();
1140                timerIdsForTimedObject.add(next.getPrimaryKey());
1141            }
1142        }
1143
1144        return timerIdsForTimedObject;
1145    }
1146    
1147    /**
1148     * Get the application class loader for the timed object
1149     * that created a given timer.
1150     */

1151    ClassLoader JavaDoc getTimerClassLoader(long containerId) {
1152        BaseContainer container = getContainer(containerId);
1153        return (container != null) ? container.getClassLoader() : null;
1154    }
1155
1156    TimerLocalHome getTimerBeanHome() {
1157        return timerLocalHome_;
1158    }
1159
1160    private RuntimeTimerState getTimerState(TimerPrimaryKey timerId) {
1161        return timerCache_.getTimerState(timerId);
1162    }
1163
1164    private TimerLocal findTimer(TimerPrimaryKey timerId)
1165        throws FinderException JavaDoc {
1166        return timerLocalHome_.findByPrimaryKey(timerId);
1167    }
1168
1169    //
1170
// Logic used by TimerWrapper for javax.ejb.Timer methods.
1171
//
1172

1173    void cancelTimer(TimerPrimaryKey timerId)
1174        throws FinderException JavaDoc, Exception JavaDoc {
1175
1176        // @@@ We can't assume this server instance owns the timer
1177
// so always ask the database. Investigate possible use of
1178
// timer cache for optimization.
1179

1180        // Look up timer bean from database. Throws FinderException if
1181
// timer no longer exists.
1182
TimerLocal timerBean = findTimer(timerId);
1183        cancelTimer(timerBean);
1184        
1185    }
1186
1187    private void cancelTimer(TimerLocal timerBean) throws Exception JavaDoc {
1188        if( timerBean.isCancelled() ) {
1189            // Already cancelled within this tx. Nothing more to do.
1190
} else {
1191            timerBean.cancel();
1192            timerBean.remove();
1193        }
1194    }
1195
1196    /**
1197     * Return next planned timeout for this timer. We have a fair amount
1198     * of leeway regarding the consistency of this information. We should
1199     * strive to detect the case where the timer no longer exists. However,
1200     * since the current timer instance may not even own this timer,
1201     * it's difficult to know the exact time of delivery in another server
1202     * instance. In the case of single-action timers, we return the
1203     * expiration time that was provided upon timer creation. For
1204     * periodic timers, we can derive the next scheduled fixed rate
1205     * expiration based on the initial expiration and the interval.
1206     */

1207    Date JavaDoc getNextTimeout(TimerPrimaryKey timerId) throws FinderException JavaDoc {
1208
1209        // @@@ We can't assume this server instance owns the timer
1210
// so always ask the database. Investigate possible use of
1211
// timer cache for optimization.
1212

1213        TimerLocal timerBean = findTimer(timerId);
1214        if( timerBean.isCancelled() ) {
1215            // The timer has been cancelled within this tx.
1216
throw new FinderException JavaDoc("timer " + timerId + " does not exist");
1217        }
1218
1219        Date JavaDoc initialExpiration = timerBean.getInitialExpiration();
1220
1221        Date JavaDoc nextTimeout = timerBean.repeats() ?
1222            calcNextFixedRateExpiration(initialExpiration,
1223                                        timerBean.getIntervalDuration()) :
1224            initialExpiration;
1225
1226        return nextTimeout;
1227    }
1228
1229    Serializable JavaDoc getInfo(TimerPrimaryKey timerId) throws FinderException JavaDoc {
1230        
1231        // @@@ We can't assume this server instance owns the timer
1232
// so always ask the database. Investigate possible use of
1233
// timer cache for optimization.
1234

1235        TimerLocal timerBean = findTimer(timerId);
1236        if( timerBean.isCancelled() ) {
1237            // The timer has been cancelled within this tx.
1238
throw new FinderException JavaDoc("timer " + timerId + " does not exist");
1239        }
1240
1241        return timerBean.getInfo();
1242    }
1243    
1244    boolean timerExists(TimerPrimaryKey timerId) {
1245        boolean exists = false;
1246
1247        // @@@ We can't assume this server instance owns the timer
1248
// so always ask the database. Investigate possible use of
1249
// timer cache for optimization.
1250

1251        try {
1252            TimerLocal timerBean = findTimer(timerId);
1253            // Make sure timer hasn't been cancelled within the current tx.
1254
exists = timerBean.isActive();
1255        } catch(FinderException JavaDoc fe) {
1256            exists = false;
1257        }
1258        
1259        return exists;
1260    }
1261
1262    private void removeTimerBean(TimerPrimaryKey timerId) {
1263        try {
1264            TimerLocal timerBean = findTimer(timerId);
1265            timerBean.remove();
1266        } catch(Throwable JavaDoc t) {
1267            logger.log(Level.WARNING, "ejb.remove_timer_failure",
1268                       new Object JavaDoc[] { timerId });
1269            logger.log(Level.WARNING, "", t);
1270        }
1271    }
1272
1273    private BaseContainer getContainer(long containerId) {
1274        ContainerFactory cf = Switch.getSwitch().getContainerFactory();
1275        return (BaseContainer) cf.getContainer(containerId);
1276    }
1277
1278    /**
1279     * Called from timer thread. Used to deliver ejb timeout.
1280     */

1281    private void deliverTimeout(TimerPrimaryKey timerId) {
1282
1283        if( logger.isLoggable(Level.FINE) ) {
1284            logger.log(Level.FINE, "EJBTimerService.deliverTimeout(): work "
1285                       +
1286                       "thread is processing work for timerId = " + timerId);
1287        }
1288
1289        if( shutdown_ ) {
1290            if( logger.isLoggable(Level.FINE) ) {
1291                logger.log(Level.FINE, "Cancelling timeout for " + timerId +
1292                           " due to server shutdown. Expiration " +
1293                           " will occur when server is restarted.");
1294            }
1295            return;
1296        }
1297    
1298        RuntimeTimerState timerState = getTimerState(timerId);
1299
1300        //
1301
// Make some defensive state checks. It's possible that the
1302
// timer state changed between the time that the JDK timer task expired
1303
// and we got called on this thread.
1304
//
1305

1306        if( timerState == null ) {
1307            logger.log(Level.FINE, "Timer state is NULL for timer " + timerId +
1308                       " in deliverTimeout");
1309            return;
1310        }
1311
1312        BaseContainer container = getContainer(timerState.getContainerId());
1313
1314        synchronized(timerState) {
1315            if( container == null ) {
1316                logger.log(Level.FINE, "Unknown container for timer " +
1317                           timerId + " in deliverTimeout. Expunging timer.");
1318                expungeTimer(timerId, true);
1319                return;
1320            } else if ( !timerState.isBeingDelivered() ) {
1321                logger.log(Level.FINE, "Timer state = " +
1322                           timerState.stateToString() +
1323                           "for timer " + timerId + " before callEJBTimeout");
1324                return;
1325            } else {
1326                if( logger.isLoggable(Level.FINE) ) {
1327                    logger.log(Level.FINE, "Calling ejbTimeout for timer " +
1328                               timerState);
1329                }
1330            }
1331        }
1332         
1333        try {
1334                    
1335            Switch.getSwitch().getCallFlowAgent().
1336                    requestStart(RequestType.TIMER_EJB);
1337            container.onEnteringContainer();
1338            // Need to address the case that another server instance
1339
// cancelled this timer. For maximum consistency, we will need
1340
// to do a database read before each delivery. This can have
1341
// significant performance implications, so investigate possible
1342
// reduced consistency tradeoffs.
1343
if( performDBReadBeforeTimeout) {
1344
1345                if( logger.isLoggable(Level.FINE) ) {
1346                    logger.log(Level.FINE, "For Timer :" + timerId +
1347                    ": check the database to ensure that the timer is still " +
1348                    " valid, before delivering the ejbTimeout call" );
1349                }
1350
1351                if( ! checkForTimerValidity(timerId) ) {
1352                    // The timer for which a ejbTimeout is about to be delivered
1353
// is not present in the database. This could happen in the
1354
// SE/EE case as other server instances (other than the owner)
1355
// could call a cancel on the timer - deleting the timer from
1356
// the database.
1357
// Also it is possible that the timer is now owned by some other
1358
// server instance
1359
return;
1360                }
1361            }
1362
1363            ///
1364
// Call container to invoke ejbTimeout on the bean instance.
1365
// The remaining actions are divided up into two categories :
1366
//
1367
// 1) Actions that should be done within the same transaction
1368
// context as the ejbTimeout call itself. These are
1369
// handled by having the ejb container call back on the
1370
// postEjbTimeout method after it has invoked bean.ejbTimeout
1371
// but *before* it has called postInvoke. That way any
1372
// transactional operations like setting the last update time
1373
// for periodic timers or removing a successfully delivered
1374
// single-action timer can be done within the same tx as
1375
// the ejbTimeout itself. Note that there is no requirement
1376
// that the ejbTimeout will be configured for CMT/RequiresNew.
1377
// If there isn't a container-managed transaction,
1378
// postEjbTimeout will still be called, and the database
1379
// operations will be done in their own transaction. While
1380
// partitioning the actions like this adds some complexity,
1381
// it's preferable to pushing this detailed timer semantics
1382
// into the container's callEJBTimeout logic.
1383
//
1384
// 2) Post-processing for setting up next timer delivery and
1385
// other redelivery conditions.
1386
//
1387
boolean redeliver = container.callEJBTimeout(timerState, this);
1388
1389            if( shutdown_ ) {
1390                // Server is shutting down so we can't finish processing
1391
// the timer expiration.
1392
if( logger.isLoggable(Level.FINE) ) {
1393                    logger.log(Level.FINE, "Cancelling timeout for " + timerId
1394                               +
1395                               " due to server shutdown. Expiration will " +
1396                               " occur on server restart");
1397                }
1398                return;
1399            }
1400
1401            // Resynchronize on timer state since a state change could have
1402
// happened either within ejbTimeout or somewhere else
1403

1404            timerState = getTimerState(timerId);
1405
1406            if( timerState == null ) {
1407                // This isn't an error case. The most likely reason is that
1408
// the ejbTimeout method itself cancelled the timer.
1409
logger.log(Level.FINE, "Timer no longer exists for " +
1410                           timerId + " after callEJBTimeout");
1411                return;
1412            }
1413
1414
1415            synchronized(timerState) {
1416                Date JavaDoc now = new Date JavaDoc();
1417                if( timerState.isCancelled() ) {
1418                    // nothing more to do.
1419
} else if( redeliver ) {
1420                    if( timerState.getNumFailedDeliveries() <
1421                        getMaxRedeliveries() ) {
1422                        Date JavaDoc redeliveryTimeout = new Date JavaDoc
1423                            (now.getTime() + getRedeliveryInterval());
1424                        if( logger.isLoggable(Level.FINE) ) {
1425                            logger.log(Level.FINE,"Redelivering " + timerState);
1426                        }
1427                        rescheduleTask(timerId, redeliveryTimeout);
1428                    } else {
1429                        int numDeliv = timerState.getNumFailedDeliveries() + 1;
1430                        logger.log(Level.INFO,
1431                           "ejb.timer_exceeded_max_deliveries",
1432                           new Object JavaDoc[] { timerState.toString(),
1433                                              new Integer JavaDoc(numDeliv)});
1434                        expungeTimer(timerId, true);
1435                    }
1436                } else if( timerState.isPeriodic() ) {
1437
1438                    // Any necessary transactional operations would have
1439
// been handled in postEjbTimeout callback. Here, we
1440
// just schedule the JDK timer task for the next ejbTimeout
1441

1442                    Date JavaDoc expiration = calcNextFixedRateExpiration(timerState);
1443                    scheduleTask(timerId, expiration);
1444                } else {
1445                   
1446                    // Any necessary transactional operations would have
1447
// been handled in postEjbTimeout callback. Nothing
1448
// more to do for this single-action timer that was
1449
// successfully delivered.
1450
}
1451            }
1452
1453        } catch(Exception JavaDoc e) {
1454            logger.log(Level.FINE, "callEJBTimeout threw exception " +
1455                       "for timer id " + timerId , e);
1456            expungeTimer(timerId, true);
1457        } finally {
1458            container.onLeavingContainer();
1459            Switch.getSwitch().getCallFlowAgent().requestEnd();
1460        }
1461    }
1462
1463    /**
1464     * Called from BaseContainer during callEJBTimeout after bean.ejbTimeout
1465     * but before postInvoke. NOTE that this method is called whether or not
1466     * the ejbTimeout method is configured for container-managed transactions.
1467     * This method is *NOT* called if the container has already determined
1468     * that a redelivery is necessary.
1469     *
1470     * @return true if successful , false otherwise.
1471     */

1472    boolean postEjbTimeout(TimerPrimaryKey timerId) {
1473
1474        boolean success = true;
1475
1476        if( shutdown_ ) {
1477            // Server is shutting down so we can't finish processing
1478
// the timer expiration.
1479
return success;
1480        }
1481
1482        // Resynchronize on timer state since a state change could have
1483
// happened either within ejbTimeout or somewhere else
1484

1485        RuntimeTimerState timerState = getTimerState(timerId);
1486
1487        if( timerState != null ) {
1488        
1489            // Since the ejbTimeout was called successfully increment the
1490
// delivery count
1491
BaseContainer container = getContainer(timerState.getContainerId());
1492            container.incrementDeliveredTimedObject();
1493                                  
1494            synchronized(timerState) {
1495                
1496                if( timerState.isCancelled() ) {
1497                    // nothing more to do.
1498
} else {
1499                    
1500                    try {
1501                        TimerLocal timerBean = getValidTimerFromDB( timerId );
1502                        if( null == timerBean ) {
1503                            return false;
1504                        }
1505
1506                        if( timerState.isPeriodic() ) {
1507                            Date JavaDoc now = new Date JavaDoc();
1508                            timerBean.setLastExpiration(now);
1509                            
1510                            // Since timer was successfully delivered, update
1511
// last delivery time in database if that option is
1512
// enabled.
1513
// @@@ add configuration for update-db-on-delivery
1514
if( logger.isLoggable(Level.FINE) ) {
1515                                logger.log(Level.FINE,
1516                                           "Setting last expiration " +
1517                                           " for periodic timer " + timerState +
1518                                           " to " + now);
1519                            }
1520
1521                        } else {
1522                                                        
1523                            if( logger.isLoggable(Level.FINE) ) {
1524                                logger.log(Level.FINE, "Single-action timer " +
1525                                   timerState + " was successfully delivered. "
1526                                   + " Removing...");
1527                            }
1528
1529                            // Timer has expired sucessfully, so remove it.
1530
cancelTimer(timerBean);
1531                        }
1532                    } catch(Exception JavaDoc e) {
1533                        
1534                        // @@@ i18N
1535
logger.log(Level.WARNING, "Error in post-ejbTimeout " +
1536                                   "timer processing for " + timerState, e);
1537                        success = false;
1538                    }
1539                }
1540            }
1541        }
1542
1543        return success;
1544    }
1545
1546    /**
1547     * This method is called to check if the timer is still valid.
1548     * In the SE/EE case the timer might be cancelled by any other
1549     * server instance (other than the owner server instance)
1550     * that is part of the same cluster. Until we have a messaging
1551     * system in place we would have to do a database query to
1552     * check if the timer is still valid.
1553     * Also check that the timer is owned by the current server instance
1554     *
1555     * @return false if the timer record is not found in the database,
1556     * true if the timer is still valid.
1557     */

1558    private boolean checkForTimerValidity(TimerPrimaryKey timerId) {
1559
1560        boolean result = true;
1561
1562        TimerLocal timerBean = getValidTimerFromDB( timerId );
1563        if( null == timerBean) {
1564            result = false;
1565        }
1566
1567        return result;
1568    }
1569
1570    private TimerLocal getValidTimerFromDB(TimerPrimaryKey timerId) {
1571
1572        boolean result = true;
1573        TimerLocal timerBean = null;
1574
1575        try {
1576
1577            timerBean = findTimer(timerId);
1578
1579            // There is a possibility that the same timer might be
1580
// migrated across to a different server. Hence check
1581
// that the ownerId of the timer record is the same as
1582
// the current server
1583
if( ! ( timerBean.getOwnerId().equals(
1584                ownerIdOfThisServer_) ) ) {
1585                logger.log(Level.WARNING,
1586                    "The timer (" + timerId + ") is not owned by " +
1587                    "server (" + ownerIdOfThisServer_ + ") that " +
1588                    "initiated the ejbTimeout. This timer is now " +
1589                    "owned by (" + timerBean.getOwnerId() + "). \n" +
1590                    "Hence delete the timer from " +
1591                    ownerIdOfThisServer_ + "'s cache.");
1592
1593                result = false;
1594            }
1595
1596        } catch( FinderException JavaDoc fex ) {
1597            // The timer does not exist in the database
1598
if( logger.isLoggable(Level.FINE) ) {
1599                logger.log(Level.FINE, "Timer :" + timerId +
1600                    ": has been cancelled by another server instance. " +
1601                    "Expunging the timer from " + ownerIdOfThisServer_ +
1602                    "'s cache.");
1603            }
1604
1605            result = false;
1606
1607        } finally {
1608            if( !result ) {
1609                // The timer is either not present in the database or it is now
1610
// owned by some other server instance, hence remove the cache
1611
//entry for the timer from the current server
1612
expungeTimer(timerId, false);
1613                timerBean = null;
1614            } else {
1615                if( logger.isLoggable(Level.FINE) ) {
1616                    logger.log(Level.FINE,
1617                        "The Timer :" + timerId +
1618                        ": is a valid timer for the server (" +
1619                        ownerIdOfThisServer_ + ")");
1620                }
1621            }
1622        }
1623
1624        return timerBean;
1625    }
1626
1627    /**
1628     * This method is called back from the EJBTimerTask object
1629     * on the JDK Timer Thread. Work performed in this callback
1630     * should be short-lived, so do a little bookkeeping and then
1631     * launch a separate thread to invoke ejbTimeout, etc.
1632     */

1633
1634    void taskExpired(TimerPrimaryKey timerId) {
1635        RuntimeTimerState timerState = getTimerState(timerId);
1636
1637        if( timerState != null ) {
1638            synchronized(timerState) {
1639                if( timerState.isScheduled() ) {
1640                    timerState.delivered();
1641
1642                    if( logger.isLoggable(Level.FINE) ) {
1643                        logger.log(Level.FINE,
1644                           "Adding work pool task for timer " + timerId);
1645                    }
1646
1647                    TaskExpiredWork work = new TaskExpiredWork(this, timerId);
1648                    com.sun.ejb.containers.util.ContainerWorkPool.addLast(work);
1649                } else {
1650                    logger.log(Level.FINE, "Timer " + timerId +
1651                               " is not in scheduled state. Current state = "
1652                               + timerState.stateToString());
1653                }
1654            }
1655        } else {
1656            logger.log(Level.FINE, "null timer state for timer id " + timerId);
1657        }
1658
1659        return;
1660    }
1661
1662    /**
1663     * Generate a unique key for the persistent timer object.
1664     * Key must be unique across server shutdown and startup, and
1665     * within all server instances sharing the same timer table.
1666     */

1667    private synchronized String JavaDoc getNextTimerId() {
1668
1669        if( nextTimerIdCounter_ <= 0 ) {
1670            nextTimerIdMillis_ = System.currentTimeMillis();
1671            nextTimerIdCounter_ = 1;
1672        } else {
1673            nextTimerIdCounter_++;
1674        }
1675
1676        // @@@ Add cluster ID
1677

1678        return new String JavaDoc(nextTimerIdCounter_ +
1679                          TIMER_ID_SEP + nextTimerIdMillis_ +
1680                          TIMER_ID_SEP + serverName_ +
1681                          TIMER_ID_SEP + domainName_);
1682    }
1683
1684    //
1685
// Accessors for timer service properties.
1686
//
1687
private long getMinimumDeliveryInterval() {
1688        return minimumDeliveryInterval_;
1689    }
1690
1691    private long getMaxRedeliveries() {
1692        return maxRedeliveries_;
1693    }
1694
1695    private long getRedeliveryInterval() {
1696        return redeliveryInterval_;
1697    }
1698
1699    //
1700
// This is a global cache of timer data *only for timers owned by
1701
// this server instance*. It is not transactionally
1702
// consistent. Operations requiring those semantics should query
1703
// the database for TimerBean info. Any timer for which there is an
1704
// active JDK timer task must be contained within this cache.
1705
//
1706
// Note : this class supports concurrent access.
1707
//
1708
private class TimerCache {
1709
1710        // Maps timer id to timer state.
1711
private Map JavaDoc timers_;
1712
1713        // Map of timer information per container.
1714
//
1715
// For stateless session beans and message-driven beans,
1716
// container id is mapped to a Long value representing the
1717
// number of timers.
1718
//
1719
//
1720
// For entity beans, container id is mapped to a list of
1721
// primary keys. NOTE : This list can contain duplicate primary keys
1722
// in the case where the same entity bean identity has more
1723
// than one associated timer.
1724

1725        private Map JavaDoc containerTimers_;
1726
1727        public TimerCache() {
1728            // Create unsynchronized collections. TimerCache will
1729
// provide concurrency control.
1730
timers_ = new HashMap JavaDoc();
1731            containerTimers_ = new HashMap JavaDoc();
1732        }
1733
1734        public synchronized void addTimer(TimerPrimaryKey timerId,
1735                                          RuntimeTimerState timerState) {
1736            if( logger.isLoggable(Level.FINE) ) {
1737                logger.log(Level.FINE, "Adding timer " + timerState);
1738            }
1739
1740            timers_.put(timerId, timerState);
1741
1742            Long JavaDoc containerId = new Long JavaDoc(timerState.getContainerId());
1743
1744            Object JavaDoc containerInfo = containerTimers_.get(containerId);
1745
1746            if( timerState.timedObjectIsEntity() ) {
1747                Collection JavaDoc entityBeans;
1748                if( containerInfo == null ) {
1749                    // NOTE : This list *can* contain duplicates, since
1750
// the same entity bean can be the timed object for
1751
// multiple timers.
1752
entityBeans = new ArrayList JavaDoc();
1753                    containerTimers_.put(containerId, entityBeans);
1754                } else {
1755                    entityBeans = (Collection JavaDoc) containerInfo;
1756                }
1757                entityBeans.add(timerState.getTimedObjectPrimaryKey());
1758            } else {
1759                Long JavaDoc timerCount = (containerInfo == null) ? new Long JavaDoc(1) :
1760                    new Long JavaDoc(((Long JavaDoc) containerInfo).longValue() + 1);
1761                containerTimers_.put(containerId, timerCount);
1762            }
1763
1764        }
1765
1766        /**
1767         * Remove a timer from the cache. This should be coded
1768         * defensively since it's possible it will be called multiple
1769         * times for the same timer.
1770         */

1771        public synchronized void removeTimer(TimerPrimaryKey timerId) {
1772            if( logger.isLoggable(Level.FINE) ) {
1773                logger.log(Level.FINE, "Removing timer " + timerId);
1774            }
1775
1776            RuntimeTimerState timerState = (RuntimeTimerState)
1777                timers_.remove(timerId);
1778
1779            if( timerState == null) {
1780                return;
1781            }
1782
1783            Long JavaDoc containerId = new Long JavaDoc(timerState.getContainerId());
1784            Object JavaDoc containerInfo = containerTimers_.get(containerId);
1785                
1786            if( containerInfo != null ) {
1787                if( timerState.timedObjectIsEntity() ) {
1788                    Collection JavaDoc entityBeans = (Collection JavaDoc) containerInfo;
1789                    if( entityBeans.size() == 1 ) {
1790                        // Only one left -- blow away the container.
1791
containerTimers_.remove(containerId);
1792                    } else {
1793                        // Remove a single instance of this primary key
1794
// from the list. There could still be other
1795
// instances of the same primary key.
1796
entityBeans.remove
1797                            (timerState.getTimedObjectPrimaryKey());
1798                    }
1799                } else {
1800                    long timerCount = ((Long JavaDoc) containerInfo).longValue();
1801                    if( timerCount == 1 ) {
1802                        // Only one left -- blow away the container
1803
containerTimers_.remove(containerId);
1804                    } else {
1805                        Long JavaDoc newCount = new Long JavaDoc(timerCount - 1);
1806                        containerTimers_.put(containerId, newCount);
1807                    }
1808                }
1809            }
1810        }
1811
1812        public synchronized RuntimeTimerState getTimerState(TimerPrimaryKey
1813                                                            timerId) {
1814            return (RuntimeTimerState) timers_.get(timerId);
1815        }
1816
1817        // True if the given entity bean has any timers and false otherwise.
1818
public synchronized boolean entityBeanHasTimers(long containerId,
1819                                                        Object JavaDoc pkey) {
1820            Object JavaDoc containerInfo = containerTimers_.get(new Long JavaDoc(containerId));
1821            return (containerInfo != null) ?
1822                ((Collection JavaDoc) containerInfo).contains(pkey) : false;
1823        }
1824
1825        // True if the ejb represented by this container id has any timers
1826
// and false otherwise.
1827
public synchronized boolean containerHasTimers(long containerId) {
1828            return containerTimers_.containsKey(new Long JavaDoc(containerId));
1829        }
1830
1831        // Placeholder for logic to ensure timer cache consistency.
1832
public synchronized void validate() {
1833        }
1834
1835    } //TimerCache{}
1836

1837    private File JavaDoc getTimerServiceShutdownFile()
1838        throws Exception JavaDoc
1839    {
1840        File JavaDoc timerServiceShutdownDirectory;
1841        File JavaDoc timerServiceShutdownFile;
1842
1843        InstanceEnvironment env = ApplicationServer.getServerContext().
1844                                    getInstanceEnvironment();
1845        AppsManager appsManager = new AppsManager(env, false);
1846
1847        String JavaDoc j2eeAppPath = appsManager.getLocation(appID);
1848        timerServiceShutdownDirectory = new File JavaDoc(j2eeAppPath + File.separator);
1849        timerServiceShutdownDirectory.mkdirs();
1850        timerServiceShutdownFile = new File JavaDoc(j2eeAppPath + File.separator
1851                + TIMER_SERVICE_FILE);
1852
1853        return timerServiceShutdownFile;
1854    }
1855
1856    private long getTimerServiceDownAt() {
1857        long timerServiceWentDownAt = -1;
1858        try {
1859            File JavaDoc timerServiceShutdownFile = getTimerServiceShutdownFile();
1860
1861            if (timerServiceShutdownFile.exists()) {
1862                DateFormat JavaDoc dateFormat =
1863                    new SimpleDateFormat JavaDoc(TIMER_SERVICE_DOWNTIME_FORMAT);
1864        
1865                FileReader JavaDoc fr = new FileReader JavaDoc(timerServiceShutdownFile);
1866                BufferedReader JavaDoc br = new BufferedReader JavaDoc(fr, 128);
1867                String JavaDoc line = br.readLine();
1868
1869                Date JavaDoc myDate = dateFormat.parse(line);
1870                timerServiceWentDownAt = myDate.getTime();
1871                logger.log(Level.INFO, "ejb.timer_service_last_shutdown",
1872                           new Object JavaDoc[] { line });
1873            } else {
1874                logger.log(Level.WARNING, "ejb.timer_service_shutdown_unknown",
1875                           new Object JavaDoc[] { timerServiceShutdownFile });
1876            }
1877        } catch (Throwable JavaDoc th) {
1878            logger.log(Level.WARNING, "ejb.timer_service_shutdown_unknown",
1879                       new Object JavaDoc[] { "" });
1880            logger.log(Level.WARNING, "", th);
1881        }
1882        return timerServiceWentDownAt;
1883    }
1884
1885
1886    /**
1887     * Called from TimerBeanContainer
1888     */

1889    public void onShutdown() {
1890        try {
1891            DateFormat JavaDoc dateFormat =
1892                new SimpleDateFormat JavaDoc(TIMER_SERVICE_DOWNTIME_FORMAT);
1893            String JavaDoc downTimeStr = dateFormat.format(new Date JavaDoc());
1894
1895            File JavaDoc timerServiceShutdownFile = getTimerServiceShutdownFile();
1896            FileWriter JavaDoc fw = new FileWriter JavaDoc(timerServiceShutdownFile);
1897            PrintWriter JavaDoc pw = new PrintWriter JavaDoc(fw);
1898
1899            pw.println(downTimeStr);
1900
1901            pw.flush();
1902            pw.close();
1903            fw.close();
1904            logger.log(Level.INFO, "ejb.timer_service_shutdown_msg",
1905                       new Object JavaDoc[] { downTimeStr });
1906        } catch (Throwable JavaDoc th) {
1907            logger.log(Level.WARNING, "ejb.timer_service_shutdown_unknown",
1908                       new Object JavaDoc[] { TIMER_SERVICE_FILE });
1909            logger.log(Level.WARNING, "", th);
1910        }
1911    }
1912
1913
1914    /**
1915     * This class gets a callback on a worker thread where the actual
1916     * ejbTimeout invocation will be made.
1917     */

1918    private static class TaskExpiredWork
1919        implements com.sun.enterprise.util.threadpool.Servicable
1920    {
1921        private EJBTimerService timerService_;
1922        private TimerPrimaryKey timerId_;
1923
1924        public TaskExpiredWork(EJBTimerService timerService,
1925                               TimerPrimaryKey timerId) {
1926            timerService_ = timerService;
1927            timerId_ = timerId;
1928        }
1929
1930        public void prolog() { }
1931        
1932        public void epilog() { }
1933        
1934        public void service() { run(); }
1935        
1936        public void run() {
1937            // Delegate to Timer Service.
1938
timerService_.deliverTimeout(timerId_);
1939        }
1940
1941    } // TaskExpiredWork
1942

1943}
1944
Popular Tags