KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > txtimer > TimerImpl


1 /*
2  * JBoss, Home of Professional Open Source
3  * Copyright 2005, JBoss Inc., and individual contributors as indicated
4  * by the @authors tag. See the copyright.txt in the distribution for a
5  * full listing of individual contributors.
6  *
7  * This is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This software is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this software; if not, write to the Free
19  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21  */

22 package org.jboss.ejb.txtimer;
23
24 // $Id: TimerImpl.java 43723 2006-04-12 18:15:38Z dimitris $
25

26 import java.io.Serializable JavaDoc;
27 import java.util.Date JavaDoc;
28 import java.util.Timer JavaDoc;
29 import java.util.TimerTask JavaDoc;
30
31 import javax.ejb.EJBException JavaDoc;
32 import javax.ejb.NoSuchObjectLocalException JavaDoc;
33 import javax.ejb.TimerHandle JavaDoc;
34 import javax.transaction.Status JavaDoc;
35 import javax.transaction.Synchronization JavaDoc;
36 import javax.transaction.Transaction JavaDoc;
37
38 import org.jboss.ejb.AllowedOperationsAssociation;
39 import org.jboss.logging.Logger;
40
41 /**
42  * An implementation of an EJB Timer.
43  *
44  * Internally it uses a java.util.Timer and maintains its state in
45  * a Tx manner.
46  *
47  * @author Thomas.Diesler@jboss.org
48  * @author Dimitris.Andreadis@jboss.org
49  * @version $Revision: 43723 $
50  * @since 07-Apr-2004
51  */

52 public class TimerImpl implements javax.ejb.Timer JavaDoc, Synchronization JavaDoc
53 {
54    // logging support
55
private static Logger log = Logger.getLogger(TimerImpl.class);
56
57    /**
58     * Timer states and their allowed transitions
59     * <p/>
60     * CREATED - on create
61     * CREATED -> STARTED_IN_TX - when strated with Tx
62     * CREATED -> ACTIVE - when started without Tx
63     * STARTED_IN_TX -> ACTIVE - on Tx commit
64     * STARTED_IN_TX -> CANCELED - on Tx rollback
65     * ACTIVE -> CANCELED_IN_TX - on cancel() with Tx
66     * ACTIVE -> CANCELED - on cancel() without Tx
67     * CANCELED_IN_TX -> CANCELED - on Tx commit
68     * CANCELED_IN_TX -> ACTIVE - on Tx rollback
69     * ACTIVE -> IN_TIMEOUT - on TimerTask run
70     * IN_TIMEOUT -> ACTIVE - on Tx commit if periode > 0
71     * IN_TIMEOUT -> EXPIRED -> on Tx commit if periode == 0
72     * IN_TIMEOUT -> RETRY_TIMEOUT -> on Tx rollback
73     * RETRY_TIMEOUT -> ACTIVE -> on Tx commit/rollback if periode > 0
74     * RETRY_TIMEOUT -> EXPIRED -> on Tx commit/rollback if periode == 0
75     */

76    private static final int CREATED = 0;
77    private static final int STARTED_IN_TX = 1;
78    private static final int ACTIVE = 2;
79    private static final int CANCELED_IN_TX = 3;
80    private static final int CANCELED = 4;
81    private static final int EXPIRED = 5;
82    private static final int IN_TIMEOUT = 6;
83    private static final int RETRY_TIMEOUT = 7;
84
85    private static final String JavaDoc[] TIMER_STATES = {"created", "started_in_tx", "active", "canceled_in_tx",
86                                                  "canceled", "expired", "in_timeout", "retry_timeout"};
87
88    // The initial txtimer properties
89
private TimerServiceImpl timerService;
90    private String JavaDoc timerId;
91    private TimedObjectId timedObjectId;
92    private TimedObjectInvoker timedObjectInvoker;
93    private Date JavaDoc firstTime;
94    private long periode;
95    private Serializable JavaDoc info;
96
97    private long nextExpire;
98    private int timerState;
99    private Timer JavaDoc utilTimer;
100    private int hashCode;
101
102    /**
103     * Schedules the txtimer for execution at the specified time with a specified periode.
104     */

105    TimerImpl(TimerServiceImpl timerService, String JavaDoc timerId, TimedObjectId timedObjectId, TimedObjectInvoker timedObjectInvoker, Serializable JavaDoc info)
106    {
107       this.timerService = timerService;
108       this.timerId = timerId;
109       this.timedObjectId = timedObjectId;
110       this.timedObjectInvoker = timedObjectInvoker;
111       this.info = info;
112
113       setTimerState(CREATED);
114    }
115
116    void startTimer(Date JavaDoc firstTime, long periode)
117    {
118       this.firstTime = firstTime;
119       this.nextExpire = firstTime.getTime();
120       this.periode = periode;
121
122       timerService.addTimer(this);
123       registerTimerWithTx();
124       
125       // the timer will actually go ACTIVE on tx commit
126
startInTx();
127    }
128
129    public String JavaDoc getTimerId()
130    {
131       return timerId;
132    }
133
134    public TimedObjectId getTimedObjectId()
135    {
136       return timedObjectId;
137    }
138
139    public Date JavaDoc getFirstTime()
140    {
141       return firstTime;
142    }
143
144    public long getPeriode()
145    {
146       return periode;
147    }
148
149    public long getNextExpire()
150    {
151       return nextExpire;
152    }
153
154    public Serializable JavaDoc getInfoInternal()
155    {
156       return info;
157    }
158
159    public boolean isActive()
160    {
161       return !isCanceled() && !isExpired();
162    }
163
164    public boolean isInRetry() {
165       return timerState == RETRY_TIMEOUT;
166    }
167
168    public boolean isCanceled()
169    {
170       return timerState == CANCELED_IN_TX || timerState == CANCELED;
171    }
172
173    public boolean isExpired()
174    {
175       return timerState == EXPIRED;
176    }
177
178    /**
179     * Cause the txtimer and all its associated expiration notifications to be cancelled.
180     *
181     * @throws IllegalStateException If this method is invoked while the instance is in
182     * a state that does not allow access to this method.
183     * @throws javax.ejb.NoSuchObjectLocalException
184     * If invoked on a txtimer that has expired or has been cancelled.
185     * @throws javax.ejb.EJBException If this method could not complete due to a system-level failure.
186     */

187    public void cancel() throws IllegalStateException JavaDoc, NoSuchObjectLocalException JavaDoc, EJBException JavaDoc
188    {
189       assertTimedOut();
190       assertAllowedOperation("Timer.cancel");
191       registerTimerWithTx();
192       cancelInTx();
193    }
194
195    /**
196     * Kill the timer, and remove it from the timer service
197     */

198    public void killTimer()
199    {
200       log.debug("killTimer: " + this);
201       if (timerState != EXPIRED)
202          setTimerState(CANCELED);
203       timerService.removeTimer(this);
204       utilTimer.cancel();
205    }
206
207    /**
208     * Kill the timer, do not remove from timer service
209     */

210    public void stopTimer()
211    {
212       log.debug("stopTimer: " + this);
213       if (timerState != EXPIRED)
214          setTimerState(CANCELED);
215       utilTimer.cancel();
216    }
217    
218    /**
219     * Get the number of milliseconds that will elapse before the next scheduled txtimer expiration.
220     *
221     * @return Number of milliseconds that will elapse before the next scheduled txtimer expiration.
222     * @throws IllegalStateException If this method is invoked while the instance is in
223     * a state that does not allow access to this method.
224     * @throws javax.ejb.NoSuchObjectLocalException
225     * If invoked on a txtimer that has expired or has been cancelled.
226     * @throws javax.ejb.EJBException If this method could not complete due to a system-level failure.
227     */

228    public long getTimeRemaining() throws IllegalStateException JavaDoc, NoSuchObjectLocalException JavaDoc, EJBException JavaDoc
229    {
230       assertTimedOut();
231       assertAllowedOperation("Timer.getTimeRemaining");
232       return nextExpire - System.currentTimeMillis();
233    }
234
235    /**
236     * Get the point in time at which the next txtimer expiration is scheduled to occur.
237     *
238     * @return Get the point in time at which the next txtimer expiration is scheduled to occur.
239     * @throws IllegalStateException If this method is invoked while the instance is in
240     * a state that does not allow access to this method.
241     * @throws javax.ejb.NoSuchObjectLocalException
242     * If invoked on a txtimer that has expired or has been cancelled.
243     * @throws javax.ejb.EJBException If this method could not complete due to a system-level failure.
244     */

245    public Date JavaDoc getNextTimeout() throws IllegalStateException JavaDoc, NoSuchObjectLocalException JavaDoc, EJBException JavaDoc
246    {
247       assertTimedOut();
248       assertAllowedOperation("Timer.getNextTimeout");
249       return new Date JavaDoc(nextExpire);
250    }
251
252    /**
253     * Get the information associated with the txtimer at the time of creation.
254     *
255     * @return The Serializable object that was passed in at txtimer creation, or null if the
256     * info argument passed in at txtimer creation was null.
257     * @throws IllegalStateException If this method is invoked while the instance is in
258     * a state that does not allow access to this method.
259     * @throws javax.ejb.NoSuchObjectLocalException
260     * If invoked on a txtimer that has expired or has been cancelled.
261     * @throws javax.ejb.EJBException If this method could not complete due to a system-level failure.
262     */

263    public Serializable JavaDoc getInfo() throws IllegalStateException JavaDoc, NoSuchObjectLocalException JavaDoc, EJBException JavaDoc
264    {
265       assertTimedOut();
266       assertAllowedOperation("Timer.getInfo");
267       return info;
268    }
269
270    /**
271     * Get a serializable handle to the txtimer. This handle can be used at a later time to
272     * re-obtain the txtimer reference.
273     *
274     * @return Handle of the Timer
275     * @throws IllegalStateException If this method is invoked while the instance is in
276     * a state that does not allow access to this method.
277     * @throws javax.ejb.NoSuchObjectLocalException
278     * If invoked on a txtimer that has expired or has been cancelled.
279     * @throws javax.ejb.EJBException If this method could not complete due to a system-level failure.
280     */

281    public TimerHandle JavaDoc getHandle() throws IllegalStateException JavaDoc, NoSuchObjectLocalException JavaDoc, EJBException JavaDoc
282    {
283       assertTimedOut();
284       assertAllowedOperation("Timer.getHandle");
285       return new TimerHandleImpl(this);
286    }
287
288    /**
289     * Return true if objectId, createDate, periode are equal
290     */

291    public boolean equals(Object JavaDoc obj)
292    {
293       if (obj == this) return true;
294       if (obj instanceof TimerImpl)
295       {
296          TimerImpl other = (TimerImpl)obj;
297          return hashCode() == other.hashCode();
298       }
299       return false;
300    }
301
302    /**
303     * Hash code based on the Timers invariant properties
304     */

305    public int hashCode()
306    {
307       if (hashCode == 0)
308       {
309          String JavaDoc hash = "[" + timerId + "," + timedObjectId + "," + firstTime + "," + periode + "]";
310          hashCode = hash.hashCode();
311       }
312       return hashCode;
313    }
314
315    /**
316     * Returns a string representation of the object.
317     */

318    public String JavaDoc toString()
319    {
320       long remaining = nextExpire - System.currentTimeMillis();
321       String JavaDoc retStr = "[id=" + timerId + ",target=" + timedObjectId + ",remaining=" + remaining + ",periode=" + periode +
322               "," + TIMER_STATES[timerState] + "]";
323       return retStr;
324    }
325
326    /**
327     * Register the txtimer with the current transaction
328     */

329    private void registerTimerWithTx()
330    {
331       Transaction JavaDoc tx = timerService.getTransaction();
332       if (tx != null)
333       {
334          try
335          {
336             tx.registerSynchronization(this);
337          }
338          catch (Exception JavaDoc e)
339          {
340             log.error("Cannot register txtimer with Tx: " + this);
341          }
342       }
343    }
344
345    private void setTimerState(int state)
346    {
347       log.debug("setTimerState: " + TIMER_STATES[state]);
348       timerState = state;
349
350       // get rid of the expired timer
351
if (timerState == EXPIRED)
352          killTimer();
353    }
354
355    private void startInTx()
356    {
357       utilTimer = new Timer JavaDoc();
358       
359       if (timerService.getTransaction() != null)
360       {
361          // don't schedule the timeout yet
362
setTimerState(STARTED_IN_TX);
363       }
364       else
365       {
366          scheduleTimeout();
367          setTimerState(ACTIVE);
368       }
369    }
370
371    private void cancelInTx()
372    {
373       if (timerService.getTransaction() != null)
374          setTimerState(CANCELED_IN_TX);
375       else
376          killTimer();
377    }
378
379    private void scheduleTimeout()
380    {
381       if (periode > 0)
382          utilTimer.schedule(new TimerTaskImpl(this), new Date JavaDoc(nextExpire), periode);
383       else
384          utilTimer.schedule(new TimerTaskImpl(this), new Date JavaDoc(nextExpire));
385    }
386    
387    /**
388     * Throws NoSuchObjectLocalException if the txtimer was canceled or has expired
389     */

390    private void assertTimedOut()
391    {
392       if (timerState == EXPIRED)
393          throw new NoSuchObjectLocalException JavaDoc("Timer has expired");
394       if (timerState == CANCELED_IN_TX || timerState == CANCELED)
395          throw new NoSuchObjectLocalException JavaDoc("Timer was canceled");
396    }
397
398    /**
399     * Throws an IllegalStateException if the Timer method call is not allowed in the current context
400     */

401    private void assertAllowedOperation(String JavaDoc timerMethod)
402    {
403       AllowedOperationsAssociation.assertAllowedIn(timerMethod,
404               AllowedOperationsAssociation.IN_BUSINESS_METHOD |
405               AllowedOperationsAssociation.IN_EJB_TIMEOUT |
406               AllowedOperationsAssociation.IN_SERVICE_ENDPOINT_METHOD |
407               AllowedOperationsAssociation.IN_AFTER_BEGIN |
408               AllowedOperationsAssociation.IN_BEFORE_COMPLETION |
409               AllowedOperationsAssociation.IN_EJB_POST_CREATE |
410               AllowedOperationsAssociation.IN_EJB_REMOVE |
411               AllowedOperationsAssociation.IN_EJB_LOAD |
412               AllowedOperationsAssociation.IN_EJB_STORE);
413    }
414
415    // Synchronization **************************************************************************************************
416

417    /**
418     * This method is invoked before the start of the commit or rollback
419     * process. The method invocation is done in the context of the
420     * transaction that is about to be committed or rolled back.
421     */

422    public void beforeCompletion()
423    {
424    }
425
426    /**
427     * This method is invoked after the transaction has committed or
428     * rolled back.
429     *
430     * @param status The status of the completed transaction.
431     */

432    public void afterCompletion(int status)
433    {
434       if (status == Status.STATUS_COMMITTED)
435       {
436          log.debug("commit: " + this);
437
438          switch (timerState)
439          {
440             case STARTED_IN_TX:
441                scheduleTimeout();
442                setTimerState(ACTIVE);
443                break;
444
445             case CANCELED_IN_TX:
446                killTimer();
447                break;
448
449             case IN_TIMEOUT:
450             case RETRY_TIMEOUT:
451                setTimerState(periode == 0 ? EXPIRED : ACTIVE);
452                break;
453          }
454       }
455       else if (status == Status.STATUS_ROLLEDBACK)
456       {
457          log.debug("rollback: " + this);
458
459          switch (timerState)
460          {
461             case STARTED_IN_TX:
462                killTimer();
463                break;
464                
465             case CANCELED_IN_TX:
466                setTimerState(ACTIVE);
467                break;
468                
469             case IN_TIMEOUT:
470                setTimerState(RETRY_TIMEOUT);
471                log.debug("retry: " + this);
472                timerService.retryTimeout(this);
473                break;
474                
475             case RETRY_TIMEOUT:
476                setTimerState(periode == 0 ? EXPIRED : ACTIVE);
477                break;
478          }
479       }
480    }
481       
482    // TimerTask ********************************************************************************************************
483

484    /**
485     * The TimerTask's run method is invoked by the java.util.Timer
486     */

487    private class TimerTaskImpl extends TimerTask JavaDoc
488    {
489       private TimerImpl timer;
490
491       public TimerTaskImpl(TimerImpl timer)
492       {
493          this.timer = timer;
494       }
495
496       /**
497        * The action to be performed by this txtimer task.
498        */

499       public void run()
500       {
501          log.debug("run: " + timer);
502
503          // Set next scheduled execution attempt. This is used only
504
// for reporting (getTimeRemaining()/getNextTimeout())
505
// and not from the underlying jdk timer implementation.
506
if (isActive() && periode > 0)
507          {
508             nextExpire += periode;
509          }
510          
511          // If a retry thread is in progress, we don't want to allow another
512
// interval to execute until the retry is complete. See JIRA-1926.
513
if (isInRetry())
514          {
515             log.debug("Timer in retry mode, skipping this scheduled execution");
516             return;
517          }
518          
519          if (isActive())
520          {
521             try
522             {
523                setTimerState(IN_TIMEOUT);
524                timedObjectInvoker.callTimeout(timer);
525             }
526             catch (Exception JavaDoc e)
527             {
528                log.error("Error invoking ejbTimeout: " + e.toString());
529             }
530             finally
531             {
532                if (timerState == IN_TIMEOUT)
533                {
534                   log.debug("Timer was not registered with Tx, resetting state: " + timer);
535                   setTimerState(periode == 0 ? EXPIRED : ACTIVE);
536                }
537             }
538          }
539       }
540    }
541 }
542
Popular Tags