001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999-2004 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: JStatefulSwitch.java 7900 2006-01-18 16:04:21Z durieuxp $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas_ejb.container;
025:
026: import java.rmi.NoSuchObjectException;
027: import java.rmi.RemoteException;
028: import java.util.ArrayList;
029: import java.util.Collections;
030: import java.util.Iterator;
031: import java.util.List;
032:
033: import javax.ejb.EJBException;
034: import javax.ejb.NoSuchObjectLocalException;
035: import javax.ejb.RemoveException;
036: import javax.ejb.SessionSynchronization;
037: import javax.ejb.TransactionRolledbackLocalException;
038: import javax.transaction.InvalidTransactionException;
039: import javax.transaction.RollbackException;
040: import javax.transaction.SystemException;
041: import javax.transaction.Transaction;
042:
043: import org.objectweb.transaction.jta.ResourceManagerEvent;
044: import org.objectweb.util.monolog.api.BasicLevel;
045:
046: /**
047: * JStatefulSwitch is the implementation of JSessionSwitch dedicated to the
048: * Stateful Session Bean.
049: * @author Philippe Durieux
050: */
051: public class JStatefulSwitch extends JSessionSwitch {
052:
053: private int sessionId;
054:
055: /**
056: * Count all curent accesses (transactional or not)
057: * A null value means that this instance is not currently used.
058: */
059: private int usedcount = 0;
060:
061: private JStatefulContext bctx = null;
062:
063: private Transaction currTx = null;
064:
065: private boolean mustCommit = false;
066:
067: private boolean expired = false;
068:
069: private Transaction beanTx = null; // only for bean managed tx
070:
071: private long lastaccesstime;
072:
073: private boolean passivated = false;
074:
075: /**
076: * Saved connectionList for this instance. The real connectionList
077: * is maintained in a ThreadLocal variable in ThreadData.
078: * Used only for session stateful methods, if they keep
079: * connection along several calls, not always in same thread.
080: * This list must not be shared between all instances.
081: * This info is transient because JDBC connection have not to be
082: * saved on disk at passivation time : They should be closed in
083: * ejbPassivate().
084: */
085: private List connectionList = Collections
086: .synchronizedList(new ArrayList());
087:
088: /**
089: * constructor.
090: * @param bf The Bean Factory
091: * @param sid the unique statefulSession ident
092: */
093: public JStatefulSwitch(JStatefulFactory bf) throws RemoteException {
094: super (bf);
095: if (TraceEjb.isDebugIc()) {
096: TraceEjb.interp.log(BasicLevel.DEBUG, "");
097: }
098: }
099:
100: public int getSessionId() {
101: return sessionId;
102: }
103:
104: /**
105: * @return true if instance can be passivated
106: */
107: public boolean canPassivate() {
108: if (usedcount > 0) {
109: TraceEjb.ssfpool.log(BasicLevel.DEBUG, "Victim is busy");
110: return false;
111: }
112: // Don't try to passivate if no matching session context!
113: return (!passivated && bctx != null);
114: }
115:
116: /**
117: * @return true if instance has been passivated.
118: */
119: public boolean isPassivated() {
120: return passivated;
121: }
122:
123: /**
124: * Passivate this instance
125: */
126: public synchronized boolean passivate() {
127: if (currTx != null || beanTx != null) {
128: TraceEjb.ssfpool.log(BasicLevel.DEBUG,
129: "Cannot passivate: busy");
130: return false;
131: }
132: TraceEjb.ssfpool.log(BasicLevel.DEBUG,
133: "Instance will be passivated");
134: passivated = ((JStatefulFactory) bf).passivateStateful(this );
135: if (passivated) {
136: // Disconnect instance so that it will be garbaged.
137: bctx.setInstance(null);
138: }
139: return passivated;
140: }
141:
142: /**
143: * Set the connection list associated to the current thread
144: * with the list associated to this stateful session.
145: */
146: public void pushConnectionList() {
147: if (TraceEjb.isDebugTx()) {
148: TraceEjb.tx.log(BasicLevel.DEBUG, "pushed connectionList ="
149: + connectionList);
150: }
151: bf.getTransactionManager().pushThreadLocalRMEventList(
152: connectionList);
153: }
154:
155: /**
156: * save the current connectionList for future use (next preInvoke).
157: */
158: public void popConnectionList() {
159: connectionList = bf.getTransactionManager()
160: .popThreadLocalRMEventList();
161: if (TraceEjb.isDebugTx()) {
162: TraceEjb.tx.log(BasicLevel.DEBUG, "poped connectionList ="
163: + connectionList);
164: }
165: }
166:
167: /**
168: * Save the Connection List after a create method.
169: */
170: public void setConnectionList(List cl) {
171: connectionList = cl;
172: if (TraceEjb.isDebugTx()) {
173: TraceEjb.tx.log(BasicLevel.DEBUG, "init connectionList ="
174: + connectionList);
175: }
176: }
177:
178: /**
179: * enlist all connection of the list
180: */
181: public void enlistConnections(Transaction tx) {
182: if (tx != null && connectionList != null) {
183: try {
184: for (Iterator it = connectionList.iterator(); it
185: .hasNext();) {
186: ResourceManagerEvent rme = (ResourceManagerEvent) it
187: .next();
188: if (rme != null) {
189: rme.enlistConnection(tx);
190: } else {
191: TraceEjb.tx
192: .log(BasicLevel.WARN,
193: "Null ResourceManagerEvent in Connection List");
194: }
195: }
196: } catch (SystemException e) {
197: TraceEjb.tx.log(BasicLevel.ERROR,
198: "cannot enlist connection", e);
199: }
200: }
201: // push the connection list in case a tx is started by the bean
202: // or if the method close a connection previously enlisted.
203: pushConnectionList();
204: }
205:
206: /**
207: * delist all connections of the list
208: */
209: public void delistConnections(Transaction tx) {
210: popConnectionList();
211: }
212:
213: // ===============================================================
214: // TimerEventListener implementation
215: // ===============================================================
216:
217: /**
218: * The session timeout has expired
219: * @param arg Not Used.
220: */
221: public synchronized void timeoutExpired(Object arg) {
222: if (TraceEjb.isVerbose()) {
223: TraceEjb.logger.log(BasicLevel.WARN,
224: "stateful session timeout expired");
225: }
226: mytimer = null;
227: // Do not remove if still used in a transaction
228: if (currTx != null) {
229: expired = true;
230: } else {
231: if (bctx != null) {
232: try {
233: bctx.setRemoved();
234: } catch (RemoteException e) {
235: if (TraceEjb.isVerbose()) {
236: TraceEjb.logger.log(BasicLevel.WARN,
237: "timeout expired", e);
238: }
239: } catch (RemoveException e) {
240: if (TraceEjb.isVerbose()) {
241: TraceEjb.logger.log(BasicLevel.WARN,
242: "timeout expired", e);
243: }
244: }
245: }
246: noLongerUsed();
247: }
248: }
249:
250: // ===============================================================
251: // other public methods
252: // ===============================================================
253:
254: /**
255: * @return the StatefulContext (for passivation)
256: */
257: public JStatefulContext getStatefulContext() {
258: if (sessionId == -1) {
259: throw new EJBException("This Session has been removed");
260: }
261: if (bctx == null) {
262: throw new EJBException("Already passivated");
263: }
264: return bctx;
265: }
266:
267: /**
268: * At each business method, get a BeanContext to run it
269: * @param tx The Transaction Context
270: * @return The Session Context
271: */
272: public synchronized JSessionContext getICtx(Transaction tx) {
273: if (TraceEjb.isDebugIc()) {
274: TraceEjb.interp.log(BasicLevel.DEBUG, "");
275: }
276:
277: // If session has been removed, we must throw
278: // NoSuchObject[Local]Exception
279: // to the caller.
280: if (sessionId == -1) {
281: throw new NoSuchObjectLocalException(
282: "This Session has been removed");
283: }
284:
285: lastaccesstime = System.currentTimeMillis();
286:
287: // reload Context if it was passivated
288: if (passivated) {
289: TraceEjb.ssfpool.log(BasicLevel.DEBUG,
290: "Bean has been passivated. Reactivate it");
291: ((JStatefulFactory) bf).activateStateful(this );
292: passivated = false;
293: }
294: // Check Transaction
295: checkTx(tx);
296:
297: usedcount++;
298:
299: return bctx;
300: }
301:
302: /**
303: * At each create, bind the Context to the transaction
304: * @param tx The current Transaction Context
305: * @param bctx The Context to bind
306: */
307: public synchronized void bindICtx(Transaction tx,
308: JStatefulContext bctx) {
309: TraceEjb.interp.log(BasicLevel.DEBUG, "");
310: sessionId = ((JStatefulFactory) bf).getNewSessionId(this );
311: this .bctx = bctx;
312: bctx.initSessionContext(this );
313: lastaccesstime = System.currentTimeMillis();
314: usedcount++;
315: // Check Transaction
316: checkTx(tx);
317: }
318:
319: /**
320: * Release the Context after use.
321: * @param tx The current Transaction Context
322: * @param discard if true, instance must be discarded
323: */
324: public synchronized void releaseICtx(RequestCtx req, boolean discard) {
325: TraceEjb.interp.log(BasicLevel.DEBUG, "");
326:
327: usedcount--;
328:
329: // In case getICtx failed, bctx may be null.
330: if (bctx == null) {
331: return;
332: }
333:
334: if (bctx.isMarkedRemoved() || discard) {
335: stopTimer();
336: noLongerUsed();
337: }
338: }
339:
340: /**
341: * This Session is no longer used: - unexport Remote Object - return the
342: * Session in the pool
343: */
344: public void noLongerUsed() {
345: if (TraceEjb.isDebugIc()) {
346: TraceEjb.interp.log(BasicLevel.DEBUG, "");
347: }
348:
349: // Unexport the EJBObject from the Orb
350: if (myremote != null) {
351: try {
352: myremote.unexportObject();
353: } catch (NoSuchObjectException e) {
354: TraceEjb.logger.log(BasicLevel.ERROR,
355: "unexportObject failed", e);
356: }
357: }
358:
359: // Forget transaction that could be uncommitted.
360: // Avoids to get it in another session bean later.
361: if (beanTx != null) {
362: TraceEjb.tx.log(BasicLevel.WARN,
363: "transaction not ended. forget it");
364: beanTx = null;
365: }
366:
367: // return the SessionSwitch in the pool.
368: // will be reused for another Session.
369: bf.removeEJB(this );
370:
371: // Remove this object.
372: // Stateful session contexts are not pooled. (EJB spec.)
373: ((JStatefulFactory) bf).removeStateful(sessionId);
374: bctx = null;
375: sessionId = -1;
376: passivated = false;
377: usedcount = 0;
378: }
379:
380: /**
381: * End of Transaction
382: */
383: public void txCompleted() {
384: if (TraceEjb.isDebugIc()) {
385: TraceEjb.interp.log(BasicLevel.DEBUG, "");
386: }
387: currTx = null;
388: if (expired) {
389: timeoutExpired(null); // try again
390: }
391: }
392:
393: /**
394: * This is used for remove on stateful session beans only.
395: * @return True if bean is participating in a client transaction
396: */
397: public boolean isInTransaction() {
398: return (currTx != null && !mustCommit);
399: }
400:
401: /**
402: * set a flag to remember that the transaction must be committed
403: */
404: public void setMustCommit(boolean mc) {
405: mustCommit = mc;
406: }
407:
408: /**
409: * Keep the bean opened transaction for later use in other methods. Stateful
410: * session bean may open a transaction and use it in other methods. This is
411: * called at postInvoke
412: */
413: public void saveBeanTx() {
414: if (bf.isTxBeanManaged()) {
415: if (TraceEjb.isDebugTx()) {
416: TraceEjb.tx.log(BasicLevel.DEBUG, "");
417: }
418: try {
419: beanTx = bf.getTransactionManager().suspend();
420: } catch (SystemException e) {
421: TraceEjb.logger.log(BasicLevel.ERROR,
422: "cannot suspend transaction:", e);
423: }
424: }
425: }
426:
427: /**
428: * @return the last access time in milliseconds
429: */
430: public long getLastAccessTime() {
431: return lastaccesstime;
432: }
433:
434: // ===============================================================
435: // private methods
436: // ===============================================================
437:
438: /**
439: * Check Transaction This is called at preInvoke
440: * @param tx The current Transaction Context
441: * @throws EJBException
442: * @throws TransactionRolledbackLocalException
443: */
444: private synchronized void checkTx(Transaction tx) {
445:
446: // No check if no Synchro, except for bean managed transaction.
447: if (bf.isSessionSynchro() == false) {
448: // Resume bean associated transaction.
449: // Stateful session bean may open a transaction and use it in other
450: // methods.
451: if (bf.isTxBeanManaged() && beanTx != null) {
452: if (TraceEjb.isDebugTx()) {
453: TraceEjb.tx.log(BasicLevel.DEBUG,
454: "resuming Bean Managed Tx");
455: }
456: try {
457: bf.getTransactionManager().resume(beanTx);
458: } catch (SystemException e) {
459: TraceEjb.logger.log(BasicLevel.ERROR,
460: "cannot resume transaction", e);
461: } catch (InvalidTransactionException e) {
462: TraceEjb.logger.log(BasicLevel.ERROR,
463: "Cannot resume transaction", e);
464: }
465: } else {
466: if (TraceEjb.isDebugTx()) {
467: TraceEjb.tx.log(BasicLevel.DEBUG, "no checkTx");
468: }
469: }
470: return;
471: }
472:
473: if (tx == null) {
474: if (TraceEjb.isDebugTx()) {
475: TraceEjb.tx.log(BasicLevel.DEBUG, "(No Tx)");
476: }
477: if (currTx != null) {
478: // A synchronized session must be called in the same transaction
479: // between afterBegin and beforeCompletion calls
480: TraceEjb.logger
481: .log(BasicLevel.ERROR,
482: "synchronized session called outside transaction context");
483: throw new EJBException(
484: "Synchronized session called outside transaction context");
485: }
486: } else {
487: if (TraceEjb.isDebugTx()) {
488: TraceEjb.tx.log(BasicLevel.DEBUG, "");
489: }
490: if (currTx == null) {
491: // A new transaction starts on this synchronized session
492: try {
493: SessionSynchronization ssbean = (SessionSynchronization) bctx
494: .getInstance();
495: if (ssbean == null) {
496: throw new EJBException(
497: "Instance should have been reactivated first.");
498: }
499: tx.registerSynchronization(bctx);
500: ssbean.afterBegin();
501: } catch (RollbackException e) {
502: throw new TransactionRolledbackLocalException(
503: "Session rolled back");
504: } catch (SystemException e) {
505: throw new EJBException("checkTx error", e);
506: } catch (RemoteException e) {
507: throw new EJBException("checkTx error", e);
508: }
509: currTx = tx;
510: } else {
511: // A synchronized session must be called in the same transaction
512: // between afterBegin and beforeCompletion calls
513: if (tx.equals(currTx) == false) {
514: TraceEjb.logger
515: .log(BasicLevel.ERROR,
516: "synchronized session called in another transaction context");
517: throw new EJBException(
518: "Synchronized session called in another transaction context");
519: }
520: }
521: }
522: }
523: }
|