001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999 Bull S.A.
004: * Contact: jonas-team@objectweb.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: JTimer.java 8418 2006-06-02 11:30:33Z durieuxp $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas_ejb.container;
025:
026: import java.io.File;
027: import java.io.Serializable;
028: import java.util.Date;
029:
030: import javax.ejb.EJBException;
031: import javax.ejb.NoSuchObjectLocalException;
032: import javax.ejb.Timer;
033: import javax.ejb.TimerHandle;
034: import javax.transaction.RollbackException;
035: import javax.transaction.Status;
036: import javax.transaction.Synchronization;
037: import javax.transaction.SystemException;
038: import javax.transaction.Transaction;
039: import javax.transaction.TransactionManager;
040:
041: import org.objectweb.jonas_timer.TimerEvent;
042: import org.objectweb.jonas_timer.TimerEventListener;
043: import org.objectweb.jonas_timer.TimerManager;
044: import org.objectweb.jonas_timer.TraceTimer;
045: import org.objectweb.util.monolog.api.BasicLevel;
046:
047: /**
048: * JOnAS Implementation of the Timer interface (from EJB 2.1) This is a basic
049: * implementation based on jonas_timer. A later (and better ?) implementation
050: * could be based on Quartz.
051: * @author Philippe Durieux
052: */
053: public class JTimer implements Timer, TimerEventListener,
054: Synchronization {
055:
056: /**
057: * true if "one-shot" timer
058: */
059: private boolean oneshot;
060:
061: /**
062: * period in case of repeatable timer (millisec)
063: */
064: private long period;
065:
066: /**
067: * initial duration (millisec)
068: */
069: private long initialduration;
070:
071: /**
072: * The TimerService that manages this Timer.
073: */
074: private JTimerService timerservice;
075:
076: /**
077: * Info provided at Timer Creation time.
078: */
079: private Serializable info;
080:
081: /**
082: * Jonas Timer Manager
083: */
084: private TimerManager tim = TimerManager.getInstance();
085:
086: /**
087: * jonas timer initial
088: */
089: private TimerEvent te1 = null;
090:
091: /**
092: * repeatable jonas timer
093: */
094: private TimerEvent te2 = null;
095:
096: /**
097: * Start Time of this Timer (millisec) used for Timer identification.
098: */
099: private long starttime;
100:
101: /**
102: * Next expiration time Used to compute the remaining time.
103: */
104: private long endtime;
105:
106: /**
107: * true when timer has been created inside current tx
108: */
109: private boolean createdInTx = false;
110:
111: /**
112: * true when timer has been cancelled inside current tx
113: */
114: private boolean cancelledInTx = false;
115:
116: /**
117: * true when timer has been cancelled
118: */
119: private boolean cancelled = false;
120:
121: /**
122: * TimerHandle for this Timer
123: */
124: private TimerHandle myHandle = null;
125:
126: /**
127: * File representing the Timer on disk
128: */
129: private File myfile = null;
130:
131: /**
132: * constructor
133: */
134: public JTimer(JTimerService timerservice, long initial,
135: long period, Serializable info) {
136: if (TraceTimer.isDebug()) {
137: TraceTimer.logger.log(BasicLevel.DEBUG,
138: "New JTimer initial = " + initial + ", period = "
139: + period);
140: }
141: this .timerservice = timerservice;
142: this .info = info;
143: this .period = period;
144: initialduration = initial;
145: oneshot = (period == 0);
146:
147: }
148:
149: /**
150: * Give a String representation of the Timer
151: */
152: public String toString() {
153: String ret = "Timer:";
154: ret += " starttime=" + starttime;
155: ret += " endtime=" + endtime;
156: ret += " period=" + period;
157: return ret;
158: }
159:
160: /**
161: * Set the file associated with this timer.
162: */
163: public void setFile(File file) {
164: myfile = file;
165: }
166:
167: /**
168: * @return the start time in millisec.
169: */
170: public long getStartTime() {
171: return starttime;
172: }
173:
174: /**
175: * @return the initial duration in millisec.
176: */
177: public long getInitialDuration() {
178: return initialduration;
179: }
180:
181: /**
182: * @return the period in millisec. (periodic timers only)
183: */
184: public long getPeriod() {
185: return period;
186: }
187:
188: /**
189: * @return the Jonas Timer Service that manages this Timer
190: */
191: public JTimerService getTimerService() {
192: return timerservice;
193: }
194:
195: /**
196: * start the Timer
197: */
198: public void startTimer() {
199:
200: TraceTimer.logger.log(BasicLevel.DEBUG, "");
201:
202: // If this is created inside a transaction, register this object as a synchronization.
203: TransactionManager tm = timerservice.getTransactionManager();
204: try {
205: Transaction tx = tm.getTransaction();
206: if (tx != null) {
207: tx.registerSynchronization(this );
208: createdInTx = true;
209: }
210: } catch (SystemException e) {
211: TraceTimer.logger.log(BasicLevel.ERROR,
212: "Cannot get Transaction", e);
213: } catch (IllegalStateException e) {
214: TraceTimer.logger.log(BasicLevel.ERROR,
215: "Cannot register synchronization:", e);
216: } catch (RollbackException e) {
217: TraceTimer.logger.log(BasicLevel.ERROR,
218: "transaction already rolled back", e);
219: }
220:
221: // init time
222: starttime = System.currentTimeMillis();
223: endtime = starttime + initialduration;
224:
225: // Create the jonas timer
226: te1 = tim.addTimerMs(this , initialduration, null, false);
227: }
228:
229: /**
230: * Stop a timer. Used internally.
231: */
232: public void stopTimer() {
233: if (TraceTimer.isDebug()) {
234: TraceTimer.logger.log(BasicLevel.DEBUG, "Stop JTimer");
235: }
236: cancelled = true;
237: if (te1 != null) {
238: te1.unset();
239: te1 = null;
240: }
241: if (te2 != null) {
242: te2.unset();
243: te2 = null;
244: }
245: // Remove the file
246: if (myfile != null) {
247: myfile.delete();
248: }
249: }
250:
251: /**
252: * @return true if timer has been cancelled
253: */
254: public boolean isCancelled() {
255: return (cancelled || cancelledInTx);
256: }
257:
258: // ------------------------------------------------------------------------------------
259: // The container must implement suitable equals() and hashCode() methods
260: // ------------------------------------------------------------------------------------
261:
262: /**
263: * Indicates whether some other object is "equal to" this one.
264: * @param obj - the reference object with which to compare.
265: * @return true if this object is the same as the obj argument; false
266: * otherwise.
267: */
268: public boolean equals(Object obj) {
269: if (obj instanceof JTimer) {
270: JTimer timer2 = (JTimer) obj;
271: if (timer2.getInitialDuration() != initialduration) {
272: if (TraceTimer.isDebug()) {
273: TraceTimer.logger.log(BasicLevel.DEBUG,
274: "different duration");
275: }
276: return false;
277: }
278: if (timer2.getPeriod() != period) {
279: if (TraceTimer.isDebug()) {
280: TraceTimer.logger.log(BasicLevel.DEBUG,
281: "different period");
282: }
283: return false;
284: }
285: if (timer2.getTimerService() != timerservice) {
286: if (TraceTimer.isDebug()) {
287: TraceTimer.logger.log(BasicLevel.DEBUG,
288: "different timerservice");
289: }
290: return false;
291: }
292: if (timer2.getStartTime() != starttime) {
293: if (TraceTimer.isDebug()) {
294: TraceTimer.logger.log(BasicLevel.DEBUG,
295: "different startTime");
296: }
297: return false;
298: }
299: if (TraceTimer.isDebug()) {
300: TraceTimer.logger.log(BasicLevel.DEBUG,
301: "timers are equal");
302: }
303: return true;
304: } else {
305: if (TraceTimer.isDebug()) {
306: TraceTimer.logger.log(BasicLevel.DEBUG, "not a Timer");
307: }
308: return false;
309: }
310: }
311:
312: /**
313: * Returns a hash code value for the object.
314: * @return a hash code value for this object.
315: */
316: public int hashCode() {
317: return (int) (getPeriod() / 1000);
318: }
319:
320: /**
321: * This is used to retrieve a Timer from its Handle only.
322: */
323: public boolean sameas(Object obj) {
324: if (obj instanceof JTimer) {
325: JTimer timer2 = (JTimer) obj;
326: if (timer2.getInitialDuration() != initialduration) {
327: if (TraceTimer.isDebug()) {
328: TraceTimer.logger.log(BasicLevel.DEBUG,
329: "different duration");
330: }
331: return false;
332: }
333: if (timer2.getPeriod() != period) {
334: if (TraceTimer.isDebug()) {
335: TraceTimer.logger.log(BasicLevel.DEBUG,
336: "different period");
337: }
338: return false;
339: }
340: if (timer2.getTimerService() != timerservice) {
341: if (TraceTimer.isDebug()) {
342: TraceTimer.logger.log(BasicLevel.DEBUG,
343: "different timerservice");
344: }
345: return false;
346: }
347: if (TraceTimer.isDebug()) {
348: TraceTimer.logger.log(BasicLevel.DEBUG,
349: "timers are equal");
350: }
351: return true;
352: } else {
353: if (TraceTimer.isDebug()) {
354: TraceTimer.logger.log(BasicLevel.DEBUG, "not a Timer");
355: }
356: return false;
357: }
358: }
359:
360: // ------------------------------------------------------------------------------------
361: // TimerEventListener implemetation
362: // ------------------------------------------------------------------------------------
363:
364: /**
365: * The timer has just expired.
366: */
367: public void timeoutExpired(Object arg) {
368: if (TraceTimer.isDebug()) {
369: TraceTimer.logger.log(BasicLevel.DEBUG, "JTimer expires");
370: }
371: // propagate the timeout
372: timerservice.notify(this );
373: if (cancelled) {
374: if (TraceTimer.isDebug()) {
375: TraceTimer.logger.log(BasicLevel.DEBUG,
376: "JTimer cancelled during timeout");
377: }
378: return;
379: }
380: if (te2 == null) {
381: te1 = null;
382: if (oneshot) {
383: // This Timer can be cancelled now.
384: doCancel();
385: } else {
386: // start a new jonas timer (periodic)
387: if (TraceTimer.isDebug()) {
388: TraceTimer.logger.log(BasicLevel.DEBUG,
389: "Start periodic jonas timer");
390: }
391: endtime = System.currentTimeMillis() + period;
392: te2 = tim.addTimerMs(this , period, null, true);
393: }
394: } else {
395: // recompute time of next expiration
396: endtime = System.currentTimeMillis() + period;
397: }
398: }
399:
400: // ------------------------------------------------------------------------------------
401: // Timer implemetation
402: // ------------------------------------------------------------------------------------
403:
404: /**
405: * Cause the timer and all its associated expiration notifications to be
406: * cancelled.
407: * @throws IllegalStateException the instance is in a state that does not
408: * allow access to this method.
409: * @throws NoSuchObjectLocalException If invoked on a timer that has expired
410: * or has been cancelled.
411: * @throws EJBException If this method could not complete due to a
412: * system-level failure.
413: */
414: public void cancel() throws IllegalStateException,
415: NoSuchObjectLocalException, EJBException {
416: if (cancelled || cancelledInTx) {
417: TraceTimer.logger.log(BasicLevel.DEBUG, "Timer cancelled");
418: throw new NoSuchObjectLocalException(
419: "Timer already cancelled");
420: }
421: if (TraceTimer.isDebug()) {
422: TraceTimer.logger.log(BasicLevel.DEBUG, "");
423: }
424:
425: // If this is called inside a transaction, register this object as a synchronization.
426: TransactionManager tm = timerservice.getTransactionManager();
427: try {
428: Transaction tx = tm.getTransaction();
429: if (tx != null) {
430: // will be cancelled at commit.
431: tx.registerSynchronization(this );
432: cancelledInTx = true;
433: } else {
434: doCancel();
435: }
436: } catch (SystemException e) {
437: TraceTimer.logger.log(BasicLevel.ERROR,
438: "Cannot get Transaction", e);
439: } catch (IllegalStateException e) {
440: TraceTimer.logger.log(BasicLevel.ERROR,
441: "Cannot register synchronization:", e);
442: } catch (RollbackException e) {
443: TraceTimer.logger.log(BasicLevel.ERROR,
444: "transaction already rolled back", e);
445: }
446: }
447:
448: private void doCancel() {
449: stopTimer();
450: timerservice.remove(this );
451: }
452:
453: /**
454: * Get the number of milliseconds that will elapse before the next scheduled
455: * timer expiration.
456: * @return the number of milliseconds that will elapse before the next
457: * scheduled timer expiration.
458: * @throws IllegalStateException the instance is in a state that does not
459: * allow access to this method.
460: * @throws NoSuchObjectLocalException If invoked on a timer that has expired
461: * or has been cancelled.
462: * @throws EJBException If this method could not complete due to a
463: * system-level failure.
464: */
465: public long getTimeRemaining() throws IllegalStateException,
466: NoSuchObjectLocalException, EJBException {
467: if (cancelled || cancelledInTx) {
468: TraceTimer.logger.log(BasicLevel.DEBUG, "Timer cancelled");
469: throw new NoSuchObjectLocalException(
470: "Timer cancelled or expired");
471: }
472: if (TraceTimer.isDebug()) {
473: TraceTimer.logger.log(BasicLevel.DEBUG, "");
474: }
475: return endtime - System.currentTimeMillis();
476: }
477:
478: /**
479: * Get the point in time at which the next timer expiration is scheduled to
480: * occur.
481: * @return the point in time at which the next timer expiration is scheduled
482: * to occur.
483: * @throws IllegalStateException the instance is in a state that does not
484: * allow access to this method.
485: * @throws NoSuchObjectLocalException If invoked on a timer that has expired
486: * or has been cancelled.
487: * @throws EJBException If this method could not complete due to a
488: * system-level failure.
489: */
490: public Date getNextTimeout() throws IllegalStateException,
491: NoSuchObjectLocalException, EJBException {
492: if (cancelled || cancelledInTx) {
493: TraceTimer.logger.log(BasicLevel.DEBUG, "Timer cancelled");
494: throw new NoSuchObjectLocalException(
495: "Timer cancelled or expired");
496: }
497: if (TraceTimer.isDebug()) {
498: TraceTimer.logger.log(BasicLevel.DEBUG, "");
499: }
500: return new Date(endtime);
501: }
502:
503: /**
504: * Get the information associated with the timer at the time of creation.
505: * @return The Serializable object that was passed in at timer creation, or
506: * null if the info argument passed in at timer creation was null.
507: * @throws IllegalStateException the instance is in a state that does not
508: * allow access to this method.
509: * @throws NoSuchObjectLocalException If invoked on a timer that has expired
510: * or has been cancelled.
511: * @throws EJBException If this method could not complete due to a
512: * system-level failure.
513: */
514: public Serializable getInfo() throws IllegalStateException,
515: NoSuchObjectLocalException, EJBException {
516: if (cancelled || cancelledInTx) {
517: TraceTimer.logger.log(BasicLevel.DEBUG, "Timer cancelled");
518: throw new NoSuchObjectLocalException(
519: "Timer cancelled or expired");
520: }
521: if (TraceTimer.isDebug()) {
522: TraceTimer.logger.log(BasicLevel.DEBUG, "");
523: }
524: return info;
525: }
526:
527: /**
528: * Get a serializable handle to the timer. This handle can be used at a
529: * later time to re-obtain the timer reference.
530: * @return a serializable handle to the timer.
531: * @throws IllegalStateException the instance is in a state that does not
532: * allow access to this method.
533: * @throws NoSuchObjectLocalException If invoked on a timer that has expired
534: * or has been cancelled.
535: * @throws EJBException If this method could not complete due to a
536: * system-level failure.
537: */
538: public TimerHandle getHandle() throws IllegalStateException,
539: NoSuchObjectLocalException, EJBException {
540: if (cancelled || cancelledInTx) {
541: TraceTimer.logger.log(BasicLevel.DEBUG, "Timer cancelled");
542: throw new NoSuchObjectLocalException(
543: "Timer cancelled or expired");
544: }
545: if (TraceTimer.isDebug()) {
546: TraceTimer.logger.log(BasicLevel.DEBUG, "");
547: }
548: if (myHandle == null) {
549: myHandle = new JTimerHandle(starttime, initialduration,
550: period, info, timerservice.getEjbName(),
551: timerservice.getContainer(), timerservice.getPK());
552: }
553: return myHandle;
554: }
555:
556: // ------------------------------------------------------------------------------------
557: // Synchronization implemetation
558: // ------------------------------------------------------------------------------------
559:
560: /**
561: * The afterCompletion method is called by the transaction manager after the
562: * transaction is committed or rolled back. This method executes without a
563: * transaction context.
564: * @param status The status of the transaction completion.
565: */
566: public void afterCompletion(int status) {
567: if (TraceEjb.isDebugTx()) {
568: TraceEjb.tx.log(BasicLevel.DEBUG, "");
569: }
570:
571: // Timers created in a transaction that is rolled back must be removed.
572: if (createdInTx) {
573: if (status != Status.STATUS_COMMITTED && !cancelled) {
574: doCancel();
575: }
576: createdInTx = false;
577: }
578:
579: // Cancel timers only if transaction is committed
580: if (cancelledInTx) {
581: if (status == Status.STATUS_COMMITTED && !cancelled) {
582: doCancel();
583: }
584: cancelledInTx = false;
585: }
586: }
587:
588: /*
589: * @see javax.transaction.Synchronization#beforeCompletion()
590: */
591: public void beforeCompletion() {
592: // nothing to do
593: }
594:
595: }
|