0001: /**
0002: * JOnAS: Java(TM) Open Application Server
0003: * Copyright (C) 1999 Bull S.A.
0004: * Contact: jonas-team@objectweb.org
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2.1 of the License, or any later version.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: *
0016: * You should have received a copy of the GNU Lesser General Public
0017: * License along with this library; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
0019: * USA
0020: *
0021: * --------------------------------------------------------------------------
0022: * $Id: JEntitySwitch.java 10144 2007-04-05 06:48:57Z durieuxp $
0023: * --------------------------------------------------------------------------
0024: */package org.objectweb.jonas_ejb.container;
0025:
0026: import java.rmi.NoSuchObjectException;
0027: import java.rmi.RemoteException;
0028: import java.util.ArrayList;
0029: import java.util.ConcurrentModificationException;
0030: import java.util.Iterator;
0031:
0032: import javax.ejb.EJBException;
0033: import javax.ejb.EntityBean;
0034: import javax.ejb.NoSuchObjectLocalException;
0035: import javax.ejb.ObjectNotFoundException;
0036: import javax.ejb.TimedObject;
0037: import javax.ejb.Timer;
0038: import javax.ejb.TimerService;
0039: import javax.transaction.Status;
0040: import javax.transaction.SystemException;
0041: import javax.transaction.Transaction;
0042:
0043: import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
0044: import org.objectweb.jonas_timer.TraceTimer;
0045: import org.objectweb.util.monolog.api.BasicLevel;
0046:
0047: /**
0048: * JEntitySwitch is used internally to synchronize accesses to the entity
0049: * context and thus to the entity bean instance. All parts common to EJBObject
0050: * and EJBLocalObject should be here. Different policies can be applied to
0051: * manage context/instance pairs: - only 1 pair (container manages the
0052: * transaction isolation) - 1 pair for each transaction (transaction isolation
0053: * managed by DataBase) - 2 pairs (1 for transactional accesses, 1 for non
0054: * transaction accesses)
0055: * @author Philippe Durieux
0056: * @author Philippe Coq
0057: */
0058: public abstract class JEntitySwitch {
0059:
0060: /**
0061: * The Factory for this bean
0062: */
0063: protected JEntityFactory bf;
0064:
0065: /**
0066: * The Primary Key for this bean instance.
0067: */
0068: protected Object pk = null;
0069:
0070: /**
0071: * The EJBLocalObject, or null if bean has no local interface.
0072: */
0073: protected JEntityLocal local = null;
0074:
0075: /**
0076: * The EJBObject, or null if bean has no remote interface.
0077: */
0078: protected JEntityRemote remote = null;
0079:
0080: /**
0081: * time in millisec. to keep objects in memory when not used.
0082: * After this time, objects are freed, if instances have been passivated
0083: * before.
0084: */
0085: protected long inactivityTimeout; // millisec.
0086:
0087: /**
0088: * time in millisec. to keep objects in memory without rereading them
0089: * (for RO policy only)
0090: */
0091: protected long readTimeout; // millisec.
0092:
0093: /**
0094: * shared=true if the bean can be modify outside this container.
0095: */
0096: protected boolean shared;
0097:
0098: /**
0099: * Lock policy used for this entity bean. Possible values are :
0100: * <dl>
0101: * <li>0 = LOCK_CONTAINER_READ_UNCOMMITTED (1 instance) </li>
0102: * <li>1 = LOCK_CONTAINER_SERIALIZED (1 instance) </li>
0103: * <li>2 = LOCK_CONTAINER_READ_COMMITTED (2 instances) </li>
0104: * <li>3 = LOCK_DATABASE (n instances) </li>
0105: * <li>4 = LOCK_READ_ONLY (1 instance) </li>
0106: * <li>5 = LOCK_CONTAINER_READ_WRITE (1 instance) </li>
0107: * <li>6 = LOCK_CONTAINER_SERIALIZED_TRANSACTED (1 instance) </li>
0108: * </dl>
0109: */
0110: protected int lockpolicy;
0111:
0112: /**
0113: * True if a transaction is mandatory for all modifying methods. In this
0114: * case, all methods outside tranction is considered as read-only.
0115: * Only CS policy has txUpdates=false.
0116: */
0117: protected boolean txUpdates = true;
0118:
0119: /**
0120: * The Timer Service
0121: */
0122: protected TimerService myTimerService = null;
0123:
0124: /**
0125: * nb of non transacted requests running
0126: */
0127: protected int countIH = 0;
0128:
0129: /**
0130: * nb of transacted requests running
0131: */
0132: protected int countIT = 0;
0133:
0134: /**
0135: * nb of threads waiting (synchronization)
0136: */
0137: protected int waiters = 0;
0138:
0139: /**
0140: * True if this instance may have been modified outside transactions. Avoids
0141: * to put it twice in dirty list.
0142: * Only used for CS policy (non transacted modifying methods)
0143: */
0144: protected boolean inDirtyList = false;
0145:
0146: /**
0147: * True if instance has been modified by a Transaction. This means that
0148: * reading outside transaction should reload it before use.
0149: * Only used for DB and CRC policies (non transacted instance)
0150: */
0151: protected boolean mustReload = false;
0152:
0153: /**
0154: * True if a TX need this instance currently used outside tx When the last
0155: * release is done, we must store this instance.
0156: * Only used when txUpdate is false (i.e. CS policy)
0157: */
0158: protected boolean mustStore = false;
0159:
0160: /**
0161: * True when context/instance has been discarded, to avoids that passivate
0162: * store its state on storage.
0163: */
0164: protected boolean todiscard = false;
0165:
0166: /**
0167: * transaction on which a Context has been registered.
0168: * Registration may be differed at first write in case of lazyregister=true.
0169: * This ref is reset to null when the transaction is completed.
0170: */
0171: protected Transaction runningtx = null;
0172:
0173: /**
0174: * transaction that has modified an instance
0175: */
0176: protected Transaction writingtx = null;
0177:
0178: // max time to wait when in a transaction, before checking for
0179: // a deadlock.
0180: protected long deadlockTimeout; // millisec.
0181:
0182: /**
0183: * List of all transactions currently blocked by runningtx
0184: */
0185: protected ArrayList blockedtx = new ArrayList();
0186:
0187: /**
0188: * True if this object will be garbaged and must not be used any longer.
0189: */
0190: protected boolean isremoved = false;
0191:
0192: /**
0193: * True if this object is no longer referenced by its factory, i.e.
0194: * it is no longer in the PK list.
0195: */
0196: protected boolean isdetached = false;
0197:
0198: // ident is used mainly for debugging
0199: protected static int counter = 0;
0200:
0201: protected String ident = "0000 ";
0202:
0203: /**
0204: * timestamp used to free objects when not used for a specified time.
0205: */
0206: protected long estimestamp = System.currentTimeMillis();
0207:
0208: protected final static long FEW_SECONDS = 2000;
0209:
0210: private static final int MAX_NB_RETRY = 2;
0211:
0212: /**
0213: * true if we can differ the registration at first write.
0214: * Cannot be true if shared or prefetch.
0215: */
0216: protected boolean lazyregister = false;
0217:
0218: /**
0219: * reentrant=true if a bean instance can be accessed concurrently
0220: * in the same transaction, or outside transaction.
0221: */
0222: protected boolean reentrant;
0223:
0224: abstract JEntityContext getContext4Tx(Transaction tx);
0225:
0226: abstract void setContext4Tx(Transaction tx, JEntityContext ctx);
0227:
0228: abstract void removeContext4Tx(Transaction tx);
0229:
0230: abstract protected void initpolicy(JEntityFactory bf);
0231:
0232: /**
0233: * @param store True if want to store instance first (CS policy)
0234: * @param passivate True if we want to passivate instance
0235: * @return result of operation: ALL_DONE, STORED, or NOT_DONE
0236: */
0237: abstract public int passivateIH(boolean store, boolean passivate);
0238:
0239: /**
0240: * Called only for CS policy, after passivateIH
0241: */
0242: abstract public void endIH();
0243:
0244: // Results from passivateIH
0245: static final int ALL_DONE = 0;
0246: static final int STORED = 1;
0247: static final int NOT_DONE = 2;
0248:
0249: /**
0250: * empty constructor. Object is initialized via init() because it is
0251: * implemented differently according to jorm mappers.
0252: */
0253: public JEntitySwitch() {
0254: }
0255:
0256: /**
0257: * constructor. A new object is build when a new PK is known in the
0258: * container, either when a new bean is created, or when a find occurs. For
0259: * create(), PK is not known yet when this object is build.
0260: * @param bf The Entity Factory
0261: * @param pk The Primary Key
0262: */
0263: public void init(JEntityFactory bf, Object pk) {
0264: this .bf = bf;
0265: this .pk = pk;
0266: isremoved = false;
0267: if (pk == null) {
0268: TraceEjb.logger.log(BasicLevel.ERROR,
0269: "Init Entity Switch with a null PK!");
0270: throw new EJBException("Init Entity Switch with a null PK!");
0271: }
0272:
0273: shared = bf.isShared();
0274: reentrant = bf.isReentrant();
0275: inactivityTimeout = bf.getInactivityTimeout() * 1000;
0276: deadlockTimeout = bf.getDeadlockTimeout() * 1000;
0277: readTimeout = bf.getReadTimeout() * 1000;
0278: initpolicy(bf);
0279: if (TraceEjb.isDebugSynchro() || TraceEjb.isDebugTx()) {
0280: // generate new ident (debug)
0281: String pks = pk.toString();
0282: ident = "<" + counter + ":" + pks + "> ";
0283: }
0284: counter++;
0285:
0286: countIH = 0;
0287: countIT = 0;
0288: waiters = 0;
0289:
0290: // Create EJBObject if bean has a Remote Interface
0291: if (bf.getHome() != null) {
0292: try {
0293: remote = ((JEntityHome) bf.getHome())
0294: .createRemoteObject();
0295: remote.setEntitySwitch(this );
0296: } catch (RemoteException e) {
0297: throw new EJBException("cannot create Remote Object", e);
0298: }
0299: }
0300:
0301: // Create EJBLocalObject if bean has a Local Interface
0302: if (bf.getLocalHome() != null) {
0303: local = ((JEntityLocalHome) bf.getLocalHome())
0304: .createLocalObject();
0305: local.setEntitySwitch(this );
0306: }
0307: // Set the timestamp to a big value because instance will be used.
0308: // This solve a problem in case of findAll() for example :
0309: // Avoids to discard instance just before it will be used.
0310: estimestamp = System.currentTimeMillis() + FEW_SECONDS;
0311: }
0312:
0313: /**
0314: * @return true if lazy registering enabled.
0315: */
0316: public boolean lazyRegistering() {
0317: return lazyregister;
0318: }
0319:
0320: /**
0321: * @return the underlaying EJBLocalObject
0322: */
0323: public JEntityLocal getLocal() {
0324: return local;
0325: }
0326:
0327: /**
0328: * @return the underlaying EJBObject
0329: */
0330: public JEntityRemote getRemote() {
0331: return remote;
0332: }
0333:
0334: /**
0335: * Obtains the TimerService associated for this Entity Bean (one / pk)
0336: * @return a JTimerService instance.
0337: */
0338: public TimerService getEntityTimerService() {
0339: // In case this object is used for a finder method,
0340: // pk should be null, and TimerService must not be retrieved.
0341: if (pk == null) {
0342: throw new java.lang.IllegalStateException();
0343: }
0344: if (myTimerService == null) {
0345: // TODO : Check that instance implements TimedObject ?
0346: myTimerService = new JTimerService(this );
0347: }
0348: return myTimerService;
0349: }
0350:
0351: /**
0352: * This transaction has just modified this instance. (CMP2 only)
0353: * Called only if lazyRegister is set and not RO policy.
0354: * @param tx transaction
0355: */
0356: public synchronized void notifyWriting(Transaction tx,
0357: JEntityContext bctx) {
0358: TraceEjb.synchro.log(BasicLevel.DEBUG, ident);
0359: if (writingtx != null) {
0360: // If same tx, we have been called for nothing because
0361: // we are already registered (dirty=true)
0362: if (!tx.equals(writingtx)) {
0363: TraceEjb.logger.log(BasicLevel.WARN, "Conflict with "
0364: + writingtx);
0365: TraceEjb.logger.log(BasicLevel.WARN, "Current Tx is "
0366: + tx);
0367: try {
0368: tx.setRollbackOnly();
0369: } catch (SystemException e) {
0370: TraceEjb.logger
0371: .log(BasicLevel.ERROR,
0372: "cannot set current transaction RollbackOnly");
0373: }
0374: // Don't rollback writingtx, because no change has been done yet by tx.
0375: // This works only if setDirty is always called before modifications.
0376: throw new EJBException("Conflict writing entity bean");
0377: }
0378: } else {
0379: if (lazyregister) {
0380: if (TraceEjb.isDebugTx()) {
0381: TraceEjb.tx.log(BasicLevel.DEBUG, ident
0382: + " Register Ctx on " + tx);
0383: }
0384: registerCtx(tx, bctx);
0385: }
0386: if (TraceEjb.isDebugTx()) {
0387: TraceEjb.tx.log(BasicLevel.DEBUG, ident
0388: + " Set writingtx");
0389: }
0390: writingtx = tx;
0391: }
0392:
0393: }
0394:
0395: /**
0396: * Notify a timeout for this bean and this Pk
0397: * @param timer timer whose expiration caused this notification.
0398: */
0399: public void notifyTimeout(Timer timer) {
0400: if (bf.isStopped()) {
0401: TraceTimer.logger
0402: .log(BasicLevel.DEBUG, "Container stopped");
0403: return;
0404: }
0405: if (isremoved) {
0406: TraceTimer.logger.log(BasicLevel.DEBUG, "Bean is removed");
0407: return;
0408: }
0409: if (TraceTimer.isDebug()) {
0410: TraceTimer.logger.log(BasicLevel.DEBUG, ident);
0411: }
0412:
0413: boolean committed = false;
0414: for (int nbretry = 0; !committed && nbretry < MAX_NB_RETRY; nbretry++) {
0415: RequestCtx rctx = bf.preInvoke(bf.getTimerTxAttribute());
0416: try {
0417: JEntityContext bctx = getICtx(rctx.currTx, false);
0418: EntityBean eb = bctx.getInstance();
0419: bf.checkSecurity(null);
0420: if (eb instanceof TimedObject) {
0421: TimedObject instance = (TimedObject) eb;
0422: instance.ejbTimeout(timer);
0423: } else {
0424: throw new EJBException(
0425: "The bean does not implement the `TimedObject` interface");
0426: }
0427: committed = (rctx.currTx == null)
0428: || (rctx.currTx.getStatus() != Status.STATUS_MARKED_ROLLBACK);
0429: } catch (EJBException e) {
0430: rctx.sysExc = e;
0431: throw e;
0432: } catch (RuntimeException e) {
0433: rctx.sysExc = e;
0434: throw new EJBException(
0435: "RuntimeException thrown by an enterprise Bean",
0436: e);
0437: } catch (Error e) {
0438: rctx.sysExc = e;
0439: throw new EJBException(
0440: "Error thrown by an enterprise Bean" + e);
0441: } catch (RemoteException e) {
0442: rctx.sysExc = e;
0443: throw new EJBException("Remote Exception raised:", e);
0444: } catch (SystemException e) {
0445: rctx.sysExc = e;
0446: throw new EJBException(
0447: "Cannot get transaction status:", e);
0448: } finally {
0449: try {
0450: bf.postInvoke(rctx);
0451: } finally {
0452: releaseICtx(rctx.currTx, rctx.sysExc != null);
0453: }
0454: }
0455: }
0456: }
0457:
0458: /**
0459: * @return the Primary Key Object for this instance.
0460: */
0461: public Object getPrimaryKey() {
0462: if (pk == null) {
0463: throw new java.lang.IllegalStateException();
0464: }
0465: return pk;
0466: }
0467:
0468: /**
0469: * bind a JEntityContext for a create method.
0470: * @param tx - the Transaction object
0471: * @param bctx - the JEntityContext to bind
0472: */
0473: public void bindICtx(Transaction tx, JEntityContext bctx) {
0474: if (TraceEjb.isDebugSynchro()) {
0475: TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " tx=" + tx);
0476: }
0477: mapICtx(tx, bctx, true, false, false);
0478: }
0479:
0480: /**
0481: * Try to bind a JEntityContext if none already bound. Called by finder
0482: * methods. This is actually kind of optimization.
0483: * Can be bypassed if problems: just return false.
0484: * @param tx - the Transaction object
0485: * @param bctx The Entity Context
0486: * @param simple True if simple finder method
0487: * @return true if context has been bound to this EntitySwitch.
0488: */
0489: public synchronized boolean tryBindICtx(Transaction tx,
0490: JEntityContext bctx, boolean simple)
0491: throws ObjectNotFoundException {
0492:
0493: if (TraceEjb.isDebugSynchro()) {
0494: TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " tx=" + tx);
0495: }
0496:
0497: // Don't bind if already a context.
0498: if (getContext4Tx(tx) != null) {
0499: if (TraceEjb.isDebugContext()) {
0500: TraceEjb.context.log(BasicLevel.DEBUG, ident
0501: + "context already mapped!");
0502: }
0503: if (getContext4Tx(tx).isMarkedRemoved()) {
0504: if (TraceEjb.isDebugContext()) {
0505: TraceEjb.context.log(BasicLevel.DEBUG, ident
0506: + " currently being removed");
0507: }
0508: if (simple) {
0509: throw new ObjectNotFoundException(
0510: "Instance is currently being removed");
0511: } else {
0512: // Don't throw exception in case of finder multiple.
0513: return false;
0514: }
0515: }
0516: // bctx will be released by caller. (See JEntityHome.vm)
0517: return false;
0518: }
0519:
0520: // No synchronization, since this is used for readonly methods (finder);
0521: boolean isdirty = false;
0522: try {
0523: isdirty = bctx.initEntityContext(this );
0524: bctx.activate(true);
0525: } catch (Exception e) {
0526: TraceEjb.synchro.log(BasicLevel.WARN, ident
0527: + "Cannot bind Ctx: " + e);
0528: return false;
0529: }
0530: setContext4Tx(tx, bctx); // after activate (CRU => full parallellism)
0531: if (!lazyregister || isdirty) {
0532: if (tx == null) {
0533: if (TraceEjb.isDebugSynchro()) {
0534: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0535: + "IH find");
0536: }
0537: } else {
0538: if (TraceEjb.isDebugTx()) {
0539: TraceEjb.tx.log(BasicLevel.DEBUG, ident
0540: + "IT find: registerCtx");
0541: }
0542: registerCtx(tx, bctx);
0543: }
0544: }
0545: return true;
0546: }
0547:
0548: /**
0549: * bind a JEntityContext for a remove method. called in case of remove(pk)
0550: * or remove(handle)
0551: * @param tx - the Transaction object
0552: * @param newctx - the JEntityContext to bind
0553: * @return the BeanContext
0554: */
0555: public JEntityContext getICtx(Transaction tx, JEntityContext newctx) {
0556: return mapICtx(tx, newctx, false, false, false);
0557: }
0558:
0559: /**
0560: * Get a context/instance associated with this transaction Called at each
0561: * request on the bean (including remove)
0562: * @param tx - the Transaction object
0563: * @param checkr - true if we must check non-reentrance.
0564: * @return the BeanContext
0565: */
0566: public JEntityContext getICtx(Transaction tx, boolean checkr) {
0567: return mapICtx(tx, null, false, true, checkr);
0568: }
0569:
0570: /**
0571: * Release completely this object, since another one will be used.
0572: * this occurs in case of create, when another EntitySwitch exist
0573: * already.
0574: * @param tx - the Transaction object
0575: */
0576: public synchronized boolean terminate(Transaction tx) {
0577: TraceEjb.synchro.log(BasicLevel.DEBUG, ident);
0578: waitmyturn(tx);
0579: JEntityContext jec = getContext4Tx(tx);
0580: if (jec != null) {
0581: if (todiscard || jec.isMarkedRemoved()) {
0582: TraceEjb.logger.log(BasicLevel.DEBUG,
0583: "will discardContext");
0584: discardContext(tx, true, true);
0585: } else {
0586: try {
0587: jec.storeIfModified();
0588: } catch (Exception e) {
0589: TraceEjb.logger.log(BasicLevel.ERROR, ident,
0590: "error while storing bean state:", e);
0591: }
0592: jec.passivate();
0593: discardContext(tx, false, true);
0594: }
0595: if (waiters > 0) {
0596: if (TraceEjb.isDebugSynchro()) {
0597: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0598: + " notify");
0599: }
0600: notifyAll();
0601: }
0602: }
0603: return true;
0604: }
0605:
0606: abstract void waitmyturn(Transaction tx);
0607:
0608: /**
0609: * Map a context and its instance.
0610: * @param tx - the Transaction object
0611: * @param bctx - the JEntityContext to bind if not null
0612: * @param forced - force to take this context. (case of create)
0613: * @param holdit - increment count to hold it, a release will be called
0614: * later.
0615: * @param checkreentrance - true if we must check non-reentrance.
0616: * @return JEntityContext actually mapped
0617: */
0618: public synchronized JEntityContext mapICtx(Transaction tx,
0619: JEntityContext bctx, boolean forced, boolean holdit,
0620: boolean checkreentrance) {
0621:
0622: try {
0623: // DEBUG only
0624: if (bf == null) {
0625: TraceEjb.synchro.log(BasicLevel.ERROR,
0626: "JEntitySwitch not initialized!");
0627: throw new EJBException("JEntitySwitch not initialized");
0628: }
0629: if (TraceEjb.isDebugSynchro()) {
0630: TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " tx="
0631: + tx);
0632: }
0633:
0634: // Check non-reentrance (See spec EJB 2.1 section 12.1.13)
0635: if (!reentrant && checkreentrance) {
0636: if (runningtx != null && countIT > 0 && tx != null
0637: && tx.equals(runningtx)) {
0638: throw new EJBException(
0639: "non-reentrant bean accessed twice in same transaction");
0640: }
0641: if (tx == null && countIH > 0) {
0642: throw new EJBException(
0643: "non-reentrant bean accessed twice outside transaction");
0644: }
0645: }
0646: // synchro (lock-policy dependant)
0647: waitmyturn(tx);
0648:
0649: // Set the timestamp to a big value because instance will be used.
0650: estimestamp = System.currentTimeMillis() + FEW_SECONDS;
0651:
0652: // Check the case where the object is detached
0653: if (isdetached) {
0654: JEntityFactory fact = (JEntityFactory) bf;
0655: JEntitySwitch old = fact
0656: .existEJB(getPrimaryKey(), this );
0657: if (old != null) {
0658: throw new NoSuchObjectLocalException(
0659: "Inactivity timeout expired");
0660: }
0661: isdetached = false;
0662: }
0663:
0664: // Choose the context to use.
0665: boolean newtrans = false;
0666: boolean isdirty = false;
0667: JEntityContext jec = getContext4Tx(tx);
0668: if (forced) {
0669: // If the new context is enforced, we must first release the older
0670: if (jec != null) {
0671: if (TraceEjb.isDebugContext()) {
0672: TraceEjb.context.log(BasicLevel.DEBUG, ident
0673: + "new context is enforced!");
0674: }
0675: discardContext(tx, false, true);
0676: }
0677: jec = bctx;
0678: setContext4Tx(tx, jec);
0679: isdirty = jec.initEntityContext(this );
0680: newtrans = true;
0681: isremoved = false; // in case of create following a remove
0682: } else {
0683: // First check if bean still exists
0684: if (isremoved) {
0685: TraceEjb.logger.log(BasicLevel.WARN, ident
0686: + " has been removed.");
0687: throw new NoSuchObjectLocalException(
0688: "Try to access a bean previously removed");
0689: }
0690: if (jec != null) {
0691: if (todiscard) {
0692: TraceEjb.logger.log(BasicLevel.WARN, ident
0693: + " has been discarded.");
0694: throw new NoSuchObjectLocalException(
0695: "Try to access a bean previously discarded");
0696: }
0697: // Reuse the Context for this transaction.
0698: // If a context was supplied, release it first.
0699: if (bctx != null) {
0700: if (TraceEjb.isDebugContext()) {
0701: TraceEjb.context.log(BasicLevel.DEBUG,
0702: ident + " a context was supplied");
0703: }
0704: if (bctx.getMyTx() != null) {
0705: TraceEjb.context.log(BasicLevel.WARN,
0706: "Will forget Tx!");
0707: }
0708: bf.releaseJContext(bctx, 2);
0709: }
0710: // In case the same instance is used for all tx, must check if
0711: // new one. For LOCK_DATABASE policy, it cannot be a new tx.
0712: if (runningtx == null
0713: && lockpolicy != EntityDesc.LOCK_DATABASE) {
0714: newtrans = true;
0715: }
0716: jec.reuseEntityContext(newtrans);
0717: } else {
0718: if (bctx != null) {
0719: jec = bctx;
0720: } else {
0721: // no Context available : get one from the pool.
0722: jec = bf.getJContext(this );
0723: }
0724: isdirty = jec.initEntityContext(this );
0725: jec.activate(true);
0726: setContext4Tx(tx, jec); // after activate
0727: newtrans = true;
0728: }
0729: }
0730:
0731: if (tx != null) {
0732: // Register Context now, except if no new transaction
0733: if (newtrans && (!lazyregister || isdirty)) {
0734: try {
0735: registerCtx(tx, jec);
0736: if (TraceEjb.isDebugSynchro()) {
0737: TraceEjb.synchro
0738: .log(
0739: BasicLevel.DEBUG,
0740: ident
0741: + "mapICtx IT: new tx, registerSynchronization");
0742: }
0743: } catch (IllegalStateException e) {
0744: if (TraceEjb.synchro
0745: .isLoggable(BasicLevel.WARN)) {
0746: TraceEjb.synchro.log(BasicLevel.WARN, ident
0747: + "mapICtx IT: not registered!", e);
0748: }
0749: }
0750: }
0751: } else {
0752: if (holdit) {
0753: if (TraceEjb.isDebugSynchro()) {
0754: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0755: + "mapICtx IH count=" + countIH);
0756: }
0757: if ((shared || mustReload) && countIH == 0) {
0758: // reload state that could have been modified by
0759: // transactions.
0760: jec.activate(false);
0761: mustReload = false;
0762: }
0763: }
0764: if (!inDirtyList && !txUpdates) {
0765: inDirtyList = true;
0766: bf.registerEJB(this );
0767: }
0768: }
0769:
0770: return jec;
0771: } finally {
0772: if (holdit) {
0773: if (tx == null) {
0774: countIH++;
0775: } else {
0776: countIT++;
0777: }
0778: }
0779: }
0780: }
0781:
0782: /**
0783: * Look if the specified transaction is blocked.
0784: * @param testedtx Transaction we look for
0785: * @return The blocking transaction
0786: */
0787: public Transaction getBlockingTx(Transaction testedtx) {
0788: Transaction ret = null;
0789: if (runningtx != null && blockedtx.size() > 0) {
0790: try {
0791: for (Iterator i = blockedtx.iterator(); i.hasNext();) {
0792: Transaction tx = (Transaction) i.next();
0793: if (tx.equals(testedtx)) {
0794: ret = runningtx;
0795: break;
0796: }
0797: }
0798: } catch (ConcurrentModificationException e) {
0799: TraceEjb.synchro.log(BasicLevel.WARN,
0800: "Concurrent access. Will retry later.");
0801: }
0802: }
0803: return ret;
0804: }
0805:
0806: /**
0807: * Release a context/instance at end of request.
0808: * @param tx - transaction associated to this context
0809: * @param discard - instance must be discarded
0810: */
0811: public synchronized void releaseICtx(Transaction tx, boolean discard) {
0812:
0813: if (tx == null) {
0814: // ------------- IH end -------------
0815: countIH--;
0816: if (TraceEjb.isDebugSynchro()) {
0817: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0818: + " countIH=" + countIH);
0819: }
0820: if (countIH == 0) {
0821: JEntityContext jec = getContext4Tx(tx);
0822: if (jec == null) {
0823: TraceEjb.context.log(BasicLevel.ERROR, ident
0824: + " No context!");
0825: Thread.dumpStack();
0826: return;
0827: }
0828: if (jec.isMarkedRemoved()) {
0829: discardContext(tx, true, true);
0830: } else if (todiscard || discard) {
0831: discardContext(tx, false, false);
0832: } else {
0833: if (mustStore) {
0834: // This is for CS policy only
0835: try {
0836: jec.storeIfModified();
0837: } catch (EJBException e) {
0838: if (TraceEjb.isVerbose()) {
0839: TraceEjb.logger.log(BasicLevel.WARN,
0840: ident + " ejbexception", e);
0841: }
0842: }
0843: mustStore = false;
0844: }
0845: if (bf.tooManyInstances()) {
0846: // Passivate if max-cache-size has been reached.
0847: if (TraceEjb.isDebugContext()) {
0848: TraceEjb.context.log(BasicLevel.DEBUG, jec
0849: + " passivated!");
0850: }
0851: jec.passivate();
0852: if (jec.getMyTx() != null) {
0853: TraceEjb.context.log(BasicLevel.WARN,
0854: "Will forget Tx!");
0855: }
0856: bf.releaseJContext(jec, 0);
0857: removeContext4Tx(tx);
0858: }
0859: }
0860: if (waiters > 0) {
0861: if (TraceEjb.isDebugSynchro()) {
0862: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0863: + " notify");
0864: }
0865: notifyAll();
0866: }
0867: } else {
0868: if (discard) {
0869: todiscard = true;
0870: }
0871: }
0872: } else {
0873: // ------------- IT end -------------
0874: // nothing to do now. Wait for tx completed.
0875: countIT--;
0876: if (TraceEjb.isDebugSynchro()) {
0877: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0878: + " countIT=" + countIT);
0879: }
0880: if (runningtx != null && discard) {
0881: if (TraceEjb.isDebugTx()) {
0882: TraceEjb.tx.log(BasicLevel.DEBUG, ident
0883: + " will be disarded when tx is over: "
0884: + runningtx);
0885: }
0886: todiscard = true;
0887: }
0888: }
0889: }
0890:
0891: public synchronized void forceDiscardICtx(Transaction tx) {
0892: discardContext(tx, true, true);
0893: }
0894:
0895: /**
0896: * This transaction is now over. We can dispose of the instance for another
0897: * transaction or discard it.
0898: * A special implementation exists for RO policy
0899: * @param tx the transaction object
0900: * @param committed true if transaction was committed.
0901: */
0902: public synchronized void txCompleted(Transaction tx,
0903: boolean committed) {
0904: JEntityContext jec = getContext4Tx(tx);
0905: if (jec == null) {
0906: TraceEjb.context.log(BasicLevel.ERROR, ident
0907: + " No context for this tx");
0908: return;
0909: }
0910: runningtx = null;
0911: if (writingtx != null && tx.equals(writingtx)) {
0912: writingtx = null;
0913: }
0914:
0915: if (TraceEjb.isDebugSynchro()) {
0916: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0917: + (committed ? " commit" : " rollback"));
0918: }
0919:
0920: // The create was eventually rolled back or instance has been discarded.
0921: // We must remove PK entry.
0922: if (todiscard || (jec.isNewInstance() && !committed)) {
0923: if (TraceEjb.isDebugTx()) {
0924: TraceEjb.tx.log(BasicLevel.DEBUG, ident
0925: + " will be discarded");
0926: }
0927: if (writingtx != null) {
0928: TraceEjb.tx.log(BasicLevel.WARN, ident
0929: + " has a running tx:" + writingtx);
0930: }
0931: discardContext(tx, !todiscard, false);
0932: return;
0933: }
0934:
0935: if (jec.isMarkedRemoved()) {
0936: if (TraceEjb.isDebugContext()) {
0937: TraceEjb.context.log(BasicLevel.DEBUG, ident
0938: + " removed!");
0939: }
0940: discardContext(tx, committed, true);
0941: } else {
0942: if (shared || !committed || bf.tooManyInstances()) {
0943: // Passivate when shared, or if max-cache-size has been reached.
0944: if (TraceEjb.isDebugContext()) {
0945: TraceEjb.context.log(BasicLevel.DEBUG, jec
0946: + " passivated!");
0947: }
0948: jec.passivate();
0949: // context is discarded if transaction rolled back
0950: if (!committed && writingtx != null) {
0951: TraceEjb.tx.log(BasicLevel.WARN, ident
0952: + " !!! Discard but has a running tx:"
0953: + writingtx);
0954: }
0955: bf.releaseJContext(jec, committed ? 2 : 0);
0956: removeContext4Tx(tx);
0957: } else {
0958: // do this before reallocating the Context for another tx
0959: // (inside this lock)
0960: jec.detachTx();
0961: }
0962: // If special instance for no transactional accesses, it must be
0963: // reloaded
0964: if (lockpolicy == EntityDesc.LOCK_CONTAINER_READ_COMMITTED
0965: || lockpolicy == EntityDesc.LOCK_DATABASE) {
0966: mustReload = true;
0967: }
0968: }
0969: if (waiters > 0) {
0970: if (TraceEjb.isDebugSynchro()) {
0971: TraceEjb.synchro.log(BasicLevel.DEBUG, ident
0972: + " notify");
0973: }
0974: notifyAll();
0975: }
0976: }
0977:
0978: /**
0979: * register a Context on the transaction, as a Synchronization. this will be
0980: * used later to store instance state when needed : before a finder, or at
0981: * beforeCompletion, and to release instance at commit.
0982: * @param tx Transaction object
0983: * @param bctx The Context to be registered
0984: */
0985: protected void registerCtx(Transaction tx, JEntityContext bctx) {
0986: // DEBUG only
0987: if (bf == null) {
0988: TraceEjb.synchro.log(BasicLevel.ERROR,
0989: "JEntitySwitch not initialized!");
0990: throw new EJBException("JEntitySwitch not initialized");
0991: }
0992: // register this context to the transaction, as a Synchronization.
0993: try {
0994: // Do this before, to test first if context still valid.
0995: if (!bctx.setRunningTx(tx)) {
0996: TraceEjb.context.log(BasicLevel.DEBUG, bctx
0997: + "already registered!");
0998: if (runningtx == null) {
0999: TraceEjb.context.log(BasicLevel.ERROR,
1000: "runningtx = null");
1001: }
1002: return;
1003: }
1004: if (bf.registerContext(tx, bctx)) {
1005: if (TraceEjb.isDebugContext()) {
1006: TraceEjb.context.log(BasicLevel.DEBUG, bctx
1007: + " registered!");
1008: }
1009: runningtx = tx;
1010: } else {
1011: TraceEjb.context.log(BasicLevel.WARN, bctx
1012: + " could not be registered!");
1013: bctx.setRunningTx(null);
1014: }
1015: } catch (IllegalStateException e) {
1016: TraceEjb.logger.log(BasicLevel.ERROR, ident
1017: + "Transaction is in an illegal state");
1018: throw e;
1019: }
1020: }
1021:
1022: /**
1023: * Detach entity switch from PK list.
1024: * This should trigger cleaning memory from all these objects by garbage collector.
1025: */
1026: protected void detachPk() {
1027: TraceEjb.swapper.log(BasicLevel.DEBUG, ident
1028: + " discarded on timeout");
1029:
1030: if (remote != null) {
1031: try {
1032: remote.unexportObject();
1033: } catch (NoSuchObjectException e) {
1034: TraceEjb.logger.log(BasicLevel.ERROR, ident
1035: + " unexport entity failed: ", e);
1036: }
1037: }
1038: bf.removeEJB(getPrimaryKey());
1039:
1040: // this Entity switch can still be reached from another bean (CMR)
1041: // In this case, we'll have to reconnect it to the PK list.
1042: isdetached = true;
1043: }
1044:
1045: /**
1046: * Discard instance/Context and free all objects.
1047: * @param tx - the Transaction object
1048: * @param forgetpk - true if remove pk from the list
1049: * @param pool - true if instance can be pooled.
1050: */
1051: protected void discardContext(Transaction tx, boolean forgetpk,
1052: boolean pool) {
1053: if (TraceEjb.isDebugTx()) {
1054: TraceEjb.tx.log(BasicLevel.DEBUG, "tx=" + tx);
1055: }
1056: JEntityContext jec = getContext4Tx(tx);
1057: if (jec != null) {
1058: if (runningtx != null && tx != null && tx.equals(runningtx)) {
1059: // This Context is registered in the tx
1060: // Must remove it from the list before releasing it.
1061: bf.unregisterContext(runningtx, jec);
1062: runningtx = null;
1063: }
1064: bf.releaseJContext(jec, pool ? 2 : 0);
1065: removeContext4Tx(tx);
1066: }
1067: if (forgetpk) {
1068: if (remote != null) {
1069: try {
1070: remote.unexportObject();
1071: } catch (NoSuchObjectException e) {
1072: TraceEjb.logger.log(BasicLevel.ERROR, ident
1073: + " unexport entity failed: ", e);
1074: }
1075: }
1076: bf.removeEJB(getPrimaryKey());
1077:
1078: // Cancel all timers for this bean (= remove the TimerService)
1079: if (myTimerService != null) {
1080: ((JTimerService) myTimerService).cancelAllTimers();
1081: myTimerService = null;
1082: }
1083: // this Entity switch must not be used any longer.
1084: isremoved = true;
1085: }
1086: todiscard = false; // done.
1087: }
1088:
1089: /**
1090: * @return lock policy for this bean
1091: */
1092: public int getPolicy() {
1093: return lockpolicy;
1094: }
1095:
1096: /**
1097: * @return State of this instance. State values are 0=in-tx, 1=out-tx, 2=idle,
1098: * 3=passive, 4=removed. we don't synchronize this method to avoid
1099: * jadmin blocks
1100: */
1101: abstract public int getState();
1102:
1103: /**
1104: * @return the JFactory
1105: */
1106: public JFactory getBeanFactory() {
1107: return bf;
1108: }
1109:
1110: }
|